1. Trait 对象
1.1 什么是Trait对象
pub trait Draw {
fn draw(&self);
}
pub struct Screen {
// Box<dyn Draw> 就是一个 trait 对象
// 它指向一个实现了 Draw trait 的类型的实例
pub components: Vec<Box<dyn Draw>>,
}
impl Screen {
pub fn run(&self) {
for component in self.components.iter() {
component.draw(); // 动态分发
}
}
}
关键特点:
- dyn Draw 表示"任何实现了 Draw trait 的类型"
- 使用 Box 在堆上分配,因为 trait 对象的大小在编译时未知
- 允许在同一个集合中存储不同类型但实现相同 trait 的对象
2. Trait对象vs泛型
2.1 使用Trait对象(动态分发)
pub struct Screen {
pub components: Vec<Box<dyn Draw>>, // 可以存储多种类型
}
impl Screen {
pub fn run(&self) {
for component in self.components.iter() {
component.draw(); // 动态分发
}
}
}
特点:
- 运行时通过虚函数表(vtable)查找方法
- 有轻微的性能开销
- 但更灵活,支持异构集合
2.2 使用泛型(静态分发)
pub struct Screen2<T: Draw> {
// 这里限制了 Vec 中必须是实现了 Draw 的同类型对象
// 比如 vec 里边全是 Button 类型的对象
pub components: Vec<T>,
}
impl<T> Screen2<T>
where
T: Draw
{
pub fn run(&self) {
for component in self.components.iter() {
component.draw(); // 静态分发
}
}
}
特点:
- 编译时进行单态化(monomorphization)
- 为每个具体类型生成专用代码
- 性能更好(无运行时开销)
3. 对象安全(Object Safety)
3.1 对象安全的规则
一个 trait 是对象安全的,当且仅当它的所有方法满足:
- 返回值类型不是 Self
- 方法没有任何泛型类型参数
// 对象安全的 trait
pub trait Draw {
fn draw(&self); // 返回 (),不是 Self
fn get_size(&self) -> (u32, u32); // 返回具体类型,不是 Self
// 没有泛型参数
}
3.2 非对象安全的例子
// 非对象安全的 trait
pub trait Clone {
fn clone(&self) -> Self; // 返回 Self,违反规则1
}
pub trait Serialize {
fn serialize<T: Writer>(&self, writer: &mut T); // 有泛型参数,违反规则2
}
// 以下代码会编译错误:
// pub struct Screen {
// pub components: Vec<Box<dyn Clone>>, // 错误!Clone 不是对象安全的
// }
4. 选择
4.1 何时使用 Trait 对象
适合使用 Trait 对象的情况:
- 需要存储不同类型的对象在同一个集合中
- 关注代码灵活性胜过极致性能
- trait 的方法不多,调用频率不高
4.2 何时使用泛型
适合使用泛型的情况:
- 性能是关键考虑因素
- 集合中只需要存储单一类型
- 方法调用频率很高