trait 对象与 dyn
你会学到什么
Box<dyn Trait>让一个集合存放多种实现了同一 trait 的类型。- trait 对象用动态分发,运行时根据实际类型决定调用哪份方法。
- 静态分发(泛型)和动态分发(dyn)各自的取舍。
最小示例
let shapes: Vec<Box<dyn Shape>> = vec![
Box::new(Circle { radius: 1.0 }),
Box::new(Square { side: 2.0 }),
];
运行代码
cd examples
cargo run -p rt_18_trait_objects
cargo test -p rt_18_trait_objects
代码讲解
泛型 Vec<T> 只能装同一种类型;当你需要把 Circle 和 Square 放进同一个 Vec,就用 trait 对象:
fn total_area(shapes: &[Box<dyn Shape>]) -> f64 {
shapes.iter().map(|shape| shape.area()).sum()
}
dyn Shape 在运行时通过虚表(vtable)找到正确的 area 实现,这叫动态分发。代价是一次指针跳转和无法内联;泛型则在编译期确定,速度更快但每种类型生成一份代码。
常见错误
把 trait 对象直接放在栈上(大小未知):
let shape: dyn Shape = Circle { radius: 1.0 }; // ❌ size not known
trait 对象必须在指针后面:Box<dyn Shape>、&dyn Shape 等。
练习
- 给
Shape再加一个Triangle实现,放进同一个Vec。 - 写一个函数返回面积最大的形状的名字。
小结
dyn Trait 提供运行时多态,适合“异构集合”和插件式设计;性能敏感且类型已知时优先用泛型。
下一步
闭包是 Rust 里随处可见的“匿名函数 + 捕获环境”。下一章学习闭包。
完整示例代码
下面是 examples/18_trait_objects/ 的完整源码。无需 clone 仓库,直接在页面上阅读、复制、对照运行。
examples/18_trait_objects/src/main.rs
//! trait 对象:用 dyn 在运行时存放不同类型。
trait Shape {
fn area(&self) -> f64;
fn name(&self) -> &str;
}
struct Circle {
radius: f64,
}
impl Shape for Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * self.radius * self.radius
}
fn name(&self) -> &str {
"circle"
}
}
struct Square {
side: f64,
}
impl Shape for Square {
fn area(&self) -> f64 {
self.side * self.side
}
fn name(&self) -> &str {
"square"
}
}
/// 一个 Vec 里放多种实现了 Shape 的类型。
fn total_area(shapes: &[Box<dyn Shape>]) -> f64 {
shapes.iter().map(|shape| shape.area()).sum()
}
fn main() {
let shapes: Vec<Box<dyn Shape>> = vec![
Box::new(Circle { radius: 1.0 }),
Box::new(Square { side: 2.0 }),
];
for shape in &shapes {
println!("{} => {:.2}", shape.name(), shape.area());
}
println!("total = {:.2}", total_area(&shapes));
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sums_mixed_shapes() {
let shapes: Vec<Box<dyn Shape>> = vec![
Box::new(Square { side: 2.0 }),
Box::new(Square { side: 3.0 }),
];
assert_eq!(total_area(&shapes), 13.0);
}
#[test]
fn reports_name() {
let circle = Circle { radius: 1.0 };
assert_eq!(circle.name(), "circle");
}
} examples/18_trait_objects/Cargo.toml
[package]
name = "rt_18_trait_objects"
version.workspace = true
edition.workspace = true
publish.workspace = true