---
title: Trait 对象与动态分发
url: https://doc.liz6.com/rust/03-advanced-generics-and-traits/02-trait-objects-and-dynamic-dispatch
locale: zh
area: rust
tags:
- rust
- 泛型与Trait进阶
date: 2026-06-30
modified: 2026-06-30
description: 泛型是编译期为每个类型生成一份代码(静态分发,零运行时开销),dyn Trait 通过 vtable 在运行时查函数指针(动态分发,一份代码服务多种类型)。object safety 决定了哪些 trait 可以做成 trait 对象,Box vs enum 是动态多态和代数多态的选择。
---

# Trait 对象与动态分发

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

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

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

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

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

```rust
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 包括：

```rust
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 的取舍

```rust
// 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<dyn>, dynamic dispatch, static dispatch, fat pointer*
