🧩 Rust 多态终极通俗指南
📚 目录导航
- 多态一句话概念
- [静态分派 vs 动态分派------根本差异](#静态分派 vs 动态分派——根本差异)
- 参数化多态(泛型)
3.1 函数里的泛型
3.2 结构体里的泛型
3.3 方法里的泛型
3.4 枚举里的泛型 - [Ad hoc 多态(特例多态)](#Ad hoc 多态(特例多态))
- [子类型多态(
dyn Trait
动态分派)](#子类型多态(dyn Trait 动态分派)) - [何时选哪种?极简速查 + 完整运行示例](#何时选哪种?极简速查 + 完整运行示例)
- [高级技巧 & 常见坑](#高级技巧 & 常见坑)
- 一句口诀快速回忆
1️⃣ 多态一句话概念
多态(Polymorphism)= "同一个调用,让不同类型各做各的事" ------调用方不用关心实现细节。
现实类比:对不同门说"开门",车门、房门、电梯门会各自打开,但指令只用写一次。
2️⃣ 静态分派 vs 动态分派------根本差异
维度 | 静态分派 | 动态分派 |
---|---|---|
关键字 | <T> 泛型、普通 trait 约束 |
dyn Trait (&dyn / Box<dyn> ...) |
决定时机 | 编译期 | 运行期 |
编译器动作 | 单态化:为每种具体类型复制/生成专属机器码 | 生成 vtable(虚函数表,保存方法指针) |
运行成本 | 零 | 每次方法调用多一次 vtable 查表 + 间接跳转 |
代表场景 | 性能敏感、类型已知 | 插件系统、异构集合、运行期才能确定类型 |
3️⃣ 参数化多态(泛型)
用占位符
<T>
写一次代码 → 编译期复制多份,零 运行开销。
3.1 函数里的泛型
rust
// <T: Copy + Mul<Output=T>> ------ T 要能 Copy 且能相乘
fn square<T: Copy + std::ops::Mul<Output = T>>(x: T) -> T {
x * x
}
fn main() {
println!("{}", square(3)); // i32
println!("{}", square(2.5)); // f64
}
运行输出
9
6.25
3.2 结构体里的泛型
rust
#[derive(Debug)]
struct Point<T> { x: T, y: T }
fn main() {
let int_pt = Point { x: 1, y: 2 }; // Point<i32>
let f_pt = Point { x: 1.0, y: 2.0 }; // Point<f64>
println!("{:?}\n{:?}", int_pt, f_pt);
}
输出
Point { x: 1, y: 2 }
Point { x: 1.0, y: 2.0 }
3.3 方法里的泛型
rust
#[derive(Debug)]
struct Point<T> { x: T, y: T }
impl<T> Point<T> {
// 方法再引入新的 U
fn mixup<U>(self, other: Point<U>) -> Point<U> {
Point { x: other.x, y: other.y }
}
}
fn main() {
let p1 = Point { x: 5, y: 10 };
let p2 = Point { x: "左", y: "右" };
let p3 = p1.mixup(p2);
println!("{:?}", p3);
}
输出
Point { x: "左", y: "右" }
3.4 枚举里的泛型
rust
#[derive(Debug)]
enum MyResult<T, E> {
Ok(T),
Err(E),
}
fn main() {
let ok : MyResult<u32, &str> = MyResult::Ok(200);
let err : MyResult<u32, &str> = MyResult::Err("网络错误");
println!("{:?}\n{:?}", ok, err);
}
输出
Ok(200)
Err("网络错误")
4️⃣ Ad hoc 多态(特例多态)
Ad hoc 来自拉丁语 ad hoc ,意为"为此而生""临时特制"。
在代码中就是:不同类型为同一接口各写一套实现。
rust
use std::fmt::Display;
trait Say { fn say(&self); } // 公共接口
impl Say for String { // 为 String 定制
fn say(&self) { println!("📢 字符串:{}", self); }
}
impl Say for i32 { // 为 i32 定制
fn say(&self) { println!("🔢 数字:{}", self); }
}
fn shout<T: Say + Display>(x: T) { // 静态分派,无 vtable
x.say();
println!("(Display 再打印一次:{})", x);
}
fn main() {
shout("Hello".to_string());
shout(42);
}
输出
📢 字符串:Hello
(Display 再打印一次:Hello)
🔢 数字:42
(Display 再打印一次:42)
5️⃣ 子类型多态(dyn Trait
动态分派)
5.1 关键词拆解
名词 | 通俗解释 |
---|---|
dyn |
dynamic,告诉编译器"运行期再决定真实类型" |
Trait 对象 | 胖指针 (数据指针, vtable 指针) |
vtable | 虚函数表:方法名 → 函数地址 |
5.2 内存示意图
Box<dyn Shape> (16 字节)
┌────────────┬────────────┐
│ data_ptr │ vtable_ptr │
└────────────┴────────────┘
data_ptr → 具体对象 (Circle / Square)
vtable_ptr→ [draw: fn, area: fn, ...]
5.3 最小示例
rust
trait Draw { fn draw(&self); } // 统一接口
struct Button { label: String }
struct Label { text: String }
impl Draw for Button {
fn draw(&self) { println!("🔘 按钮:{}", self.label); }
}
impl Draw for Label {
fn draw(&self) { println!("🏷️ 标签:{}", self.text); }
}
fn render(ui: &[Box<dyn Draw>]) { // 接受异构集合
for w in ui { w.draw(); } // 运行期查 vtable
}
fn main() {
let ui: Vec<Box<dyn Draw>> = vec![
Box::new(Button { label: "确定".into() }),
Box::new(Label { text: "版本 1.0".into() }),
];
render(&ui);
}
输出
🔘 按钮:确定
🏷️ 标签:版本 1.0
6️⃣ 何时选哪种?极简速查 + 完整运行示例
场景 | 首选方案 | 为什么 |
---|---|---|
性能极限、类型已知 | 泛型(参数化多态) | 单态化 ➜ 零成本 |
给现有类型统一接口 | Ad hoc 多态 | 各类型各写实现,仍静态分派 |
一个集合装不同类型 | Box<dyn Trait> |
运行期决定类型 |
隐藏返回值细节且想静态分派 | -> impl Trait |
API 只露能力,内部零开销 |
6.1 Box<dyn Trait>
& impl Trait
对比示例
rust
trait Animal { fn sound(&self) -> &'static str; }
// --------------------------- 动态分派 ---------------------------
struct Dog; struct Cat;
impl Animal for Dog { fn sound(&self) -> &'static str { "汪" } }
impl Animal for Cat { fn sound(&self) -> &'static str { "喵" } }
fn zoo() { // 一个笼子装不同动物
let list: Vec<Box<dyn Animal>> = vec![Box::new(Dog), Box::new(Cat)];
for a in &list { println!("{}", a.sound()); } // vtable 调度
}
// --------------------------- 隐藏返回类型 -------------------------
fn odds() -> impl Iterator<Item = i32> {
(0..6).filter(|x| x % 2 == 1) // 返回类型被隐藏
}
fn main() {
zoo(); // 动态分派示例
for n in odds() { print!("{} ", n); } // impl Trait 示例
}
输出
汪
喵
1 3 5
7️⃣ 高级技巧 & 常见坑
技巧 / 坑 | 说明 |
---|---|
Blanket Impl | impl<T: Display> ToString for T 一行给所有可 Display 的类型自动实现 to_string() |
对象安全规则 | 只有"对象安全"的 Trait 才能用 dyn Trait :① 不能有泛型方法;② 方法签名里不能直接用 Self 作为参数或返回值(除放在关联类型里) |
性能误区 | 动态分派的间接跳转成本很小,除非在紧密内层循环,否则无需过早优化 |
8️⃣ 一句口诀快速回忆
"能静不动,要扩用 Ad‑hoc;类型未知,用 dyn 出锅。"
- 参数化多态
<T>
→ 静态分派,零成本 - Ad hoc 多态
trait + impl
→ 静态分派,专属实现 - 子类型多态
dyn Trait
→ 动态分派,vtable 调度 - 隐藏返回类型
-> impl Trait
→ 静态分派 + 封装