Rust 学习笔记 23:面向对象特性 (Object Oriented Features)
“The problem with object-oriented languages is they’ve got all this implicit environment that they carry around with them. You wanted a banana but you got a gorilla holding the banana and the entire jungle.” – Joe Armstrong
Rust 是面向对象语言吗?这取决于你对 OOP 的定义。 如果定义是"封装、继承、多态",那么 Rust:
- 封装:有。
struct和impl,以及pub关键字。 - 继承:没有。Rust 没有任何继承机制(Struct 不能继承 Struct)。
- 多态:有。通过泛型(静态多态)和 Trait Objects(动态多态)。
1. Trait Objects (动态分发)
我们在泛型那一章学过用 Trait Bound 实现多态:
1fn draw<T: Draw>(item: T) { item.draw(); }
这种是静态分发 (Static Dispatch)。编译器会为每种具体的 T 生成一份代码。优点是快,缺点是 Vec<T> 里的所有元素必须是同一种类型。
如果你想在一个 Vec 里装不同类型的对象(只要它们都实现了 Draw),你就需要 Trait Object:
1pub struct Screen {
2 pub components: Vec<Box<dyn Draw>>,
3}
dyn Draw:表示"任何实现了 Draw Trait 的类型"。Box<...>:因为dyn Draw大小不确定(可能是 Button 也可能是 SelectBox),所以必须放在指针后面(如 Box 或 &)。
这被称为动态分发 (Dynamic Dispatch)。程序会在运行时通过虚表 (vtable) 查找要调用的方法。这会有微小的性能损耗,和 C++ 的虚函数类似。
2. 只有对象的安全 (Object Safety)
不是所有的 Trait 都能做成 Trait Object。 只有满足 对象安全 (Object Safe) 规则的 Trait 才可以。 主要规则:
- 方法的返回类型不是
Self。 - 方法没有任何泛型类型参数。
因为一旦变成了 Trait Object,原来的具体类型信息就丢了 (Erased),编译器不知道 Self 到底是谁。
3. 甚至能实现设计模式
虽然没有继承,但通过 Trait Objects 和内部可变性 (RefCell),Rust 依然可以优雅地实现经典设计模式,比如状态模式 (State Pattern)。
GoF 的设计模式在 Rust 中往往有"函数式"的替代方案,但这种 OOP 能力保证了 Rust 的灵活性。
4. 小结
第23篇笔记。
- Trait Objects (
dyn Trait) 是 Rust 实现运行时多态的关键。 - 它允许我们在集合中存储不同类型的对象。
- 代价是动态分发的开销和无法使用泛型某些特性的限制。
下一篇,我们将深入 模式匹配 (Pattern Matching) 的细节。我们之前用过 match,但 Rust 模式匹配的强大远超你的想象(@绑定、Guard、解构…)。
练习题:
- 修改
Screen结构体,使其成为一个泛型结构体Screen<T: Draw>,看看它和使用Box<dyn Draw>在使用上有什么区别(提示:泛型版本只能存同一种类型的组件)。 - 尝试实现一个简单的状态模式:一个
Post结构体,有Draft,PendingReview,Published三种状态。
思考题:
继承 (Inheritance) 曾被认为是 OOP 的基石,但现代语言(Go, Rust, Swift)都倾向于组合 (Composition) 和 接口 (Interface/Trait)。为什么继承越来越"不香"了?(提示:菱形继承问题、基类脆化问题)。
本文代码示例:
关注公众号:极客老墨
更多 AI 应用开发、工程实践和效率工具分享,欢迎扫码关注。
