本页目录

Trait 对象与动态分发

泛型是编译期为每个类型生成一份代码(静态分发,零运行时开销),dyn Trait 通过 vtable 在运行时查函数指针(动态分发,一份代码服务多种类型)。object safety 决定了哪些 trait 可以做成 trait 对象,Box<dyn> vs enum 是动态多态和代数多态的选择。

两种分发: 编译期 vs 运行时

// 静态分发: 泛型。编译期为每个 T 生成独立代码, 零运行时开销
fn static_dispatch<T: Display>(x: &T) { println!("{}", x); }

// 动态分发: trait object。通过指针+vtable, 有间接调用开销
fn dynamic_dispatch(x: &dyn Display) { println!("{}", x); }

什么时候用哪个?如果你在编译期知道所有可能的类型 → 静态分发。如果类型在运行时才确定,或需要异构集合⁠(不同具体类型存储在同一容器中)→ 动态分发。经典异构场景:

let items: Vec<Box<dyn Display>> = vec![
    Box::new(42_i32),
    Box::new(String::from("hello")),
    Box::new(3.14_f64),
];
for item in &items { println!("{}", item); }

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 包括:

trait NotObjectSafe {
    fn clone(&self) -> Self;         // 返回 Self — 不知道 Self 是什么
    fn generic<T>(&self, x: T);      // 泛型方法 — 需要单态化
}

// 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
enum Shape { Circle(f64), Rectangle(f64, f64) }
impl Shape {
    fn area(&self) -> f64 {
        match self { Shape::Circle(r) => PI * r * r, Shape::Rectangle(w, h) => w * h }
    }
}

// dyn: open set (可扩展), virtual dispatch (每调用一次做一次间接跳转)
trait Shape { fn area(&self) -> f64; }
let shapes: Vec<Box<dyn Shape>> = 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, dynamic dispatch, static dispatch, fat pointer