本页目录
Trait 对象与动态分发
泛型是编译期为每个类型生成一份代码(静态分发,零运行时开销),
dyn Trait通过 vtable 在运行时查函数指针(动态分发,一份代码服务多种类型)。object safety 决定了哪些 trait 可以做成 trait 对象,Box<dyn>vs enum 是动态多态和代数多态的选择。
两种分发: 编译期 vs 运行时
// 静态分发: 泛型。编译期为每个 T 生成独立代码, 零运行时开销
// 动态分发: trait object。通过指针+vtable, 有间接调用开销
什么时候用哪个?如果你在编译期知道所有可能的类型 → 静态分发。如果类型在运行时才确定,或需要异构集合(不同具体类型存储在同一容器中)→ 动态分发。经典异构场景:
let items: = vec!;
for item in &items
vtable 的内部实现
&dyn Display 在运行时是两个指针(fat pointer, 16 bytes on 64-bit):
ptr_to_data: 指向堆上的实际数据 (如 i32 或 String)
ptr_to_vtable: 指向一个静态的 vtable 结构体
vtable for i32 as Display:
[0]: drop_in_place<i32> — 如何释放
[1]: size_of::<i32> (4) — 数据大小
[2]: align_of::<i32> (4) — 对齐
[3]: <i32 as Display>::fmt — 具体的 Display::fmt 函数指针
调用 x.fmt(f) 时:读 vtable → 取第 3 个 entry → 间接跳转。比静态调用多一次指针解引用(通常可被 CPU branch predictor 预取,但仍是额外指令)。
Object Safety: 不是所有 trait 都能做 dyn
Trait object 需要满足"object safety"——因为编译器必须能生成 vtable。不满足的 trait 包括:
// let x: &dyn NotObjectSafe = ...; // ERROR: the trait is not object safe
Clone 不是 object safe 的——fn clone(&self) -> Self 返回的类型在编译时必须在调用点已知。所以你不能 Box<dyn Clone>。但你可以用 Box<dyn Any> 做类型擦除 + downcast。
dyn 与 enum 的取舍
// enum: 编译期 closed set, 无 virtual dispatch
// dyn: open set (可扩展), virtual dispatch (每调用一次做一次间接跳转)
let shapes: = vec!;
enum 适合"我知道所有可能的 variant"且 variant 数量少、不常变。dyn 适合"下游可以添加更多实现"或 variant 太多(扩展到几十上百个 enum variant 不实际)。
参考
- Rust Book: Chapter 17.2
- Rust Reference: vtable layout, object safety
- Rustonomicon: dyn Trait internals
Keywords: dyn Trait, vtable, object safety, Box