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:

  • 封装:有。structimpl,以及 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 才可以。 主要规则:

  1. 方法的返回类型不是 Self
  2. 方法没有任何泛型类型参数。

因为一旦变成了 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、解构…)。


练习题

  1. 修改 Screen 结构体,使其成为一个泛型结构体 Screen<T: Draw>,看看它和使用 Box<dyn Draw> 在使用上有什么区别(提示:泛型版本只能存同一种类型的组件)。
  2. 尝试实现一个简单的状态模式:一个 Post 结构体,有 Draft, PendingReview, Published 三种状态。

思考题

继承 (Inheritance) 曾被认为是 OOP 的基石,但现代语言(Go, Rust, Swift)都倾向于组合 (Composition) 和 接口 (Interface/Trait)。为什么继承越来越"不香"了?(提示:菱形继承问题、基类脆化问题)。


本文代码示例

关注公众号:极客老墨

更多 AI 应用开发、工程实践和效率工具分享,欢迎扫码关注。

极客老墨微信公众号二维码

相关阅读