Rust 学习笔记 15:Traits (特质)
“If it walks like a duck and quacks like a duck, it must be a Trait.”
在 Go 语言中,接口 (Interface) 是隐式实现的。只要你的方法签名对上了,你就实现了接口。
在 Rust 中,Traits (特质) 必须要显式实现 (impl Trait for Type)。
1. 定义与实现 Trait
定义一个 Summary Trait:
1pub trait Summary {
2 fn summarize(&self) -> String;
3}
为 NewsArticle 和 Tweet 实现它:
1impl Summary for Tweet {
2 fn summarize(&self) -> String {
3 format!("{}: {}", self.username, self.content)
4 }
5}
2. 默认实现 (Default Implementations)
Trait 中可以提供默认实现,这有点像 Java 8 的 default method。
1pub trait Summary {
2 fn summarize(&self) -> String {
3 String::from("(Read more...)")
4 }
5}
如果实现了 Trait 的类型不重写它,就会使用这个默认版本。
3. Trait 作为参数
要想编写接受任何实现了 Summary 的类型的函数,有两种写法。
impl Trait 语法糖:
1pub fn notify(item: &impl Summary) { ... }
Trait Bound (泛型约束):
1pub fn notify<T: Summary>(item: &T) { ... }
如果你需要两个参数是同一种类型,必须用 Trait Bound:fn foo<T: Summary>(p1: T, p2: T)。
4. 多重约束与 Where 从句
如果一个类型需要同时实现多个 Trait(比如即要能显示,又要能拷贝):
1fn notify(item: &(impl Summary + Display)) { ... }
如果约束太长,函数签名会很丑,可以用 where 从句:
1fn some_function<T, U>(t: &T, u: &U) -> i32
2where
3 T: Display + Clone,
4 U: Clone + Debug,
5{ ... }
5. 孤儿规则 (Orphan Rule)
这是 Rust 为了保证一致性的一条重要规则: 你只能为类型实现 Trait,如果 Trait 或 类型 至少有一个是在你的 Crate 中定义的。
例如:
- 你可以为如果你自定义的
Tweet实现标准库的Display。 - 你可以为标准库的
Vec实现你自定义的Summary。 - 但是,你不能为
Vec(标准库) 实现Display(标准库)。
这防止了其他人破坏你的代码行为(虽然在 Go 中可以通过 alias type 绕过,但 Rust 比较严格)。
6. 小结
第十五篇笔记。
- Trait 定义了共享行为。
- impl Trait for Type 显式实现。
- Trait Bound 是泛型的约束条件。
Trait 是 Rust 零成本抽象的灵魂。标准库中充满了 Trait:Display, Debug, Copy, Clone, Drop, Iterator… 掌握了 Trait,你就掌握了 Rust 的半壁江山。
下一篇,我们将挑战 Rust 的终极 Boss —— 生命周期 (Lifetimes)。这是泛型的一个特例,也是让无数初学者头秃的元凶。
练习题:
- 为
Pair<T>结构体实现new函数。 - 使用
impl<T: Display + PartialOrd> Pair<T>块,编写一个cmp_display方法,只有当T实现了Display和PartialOrd时,Pair<T>才有这个方法(这叫 Blanket Implementation 的一种应用)。
思考题:
Go 的接口是"鸭子类型"(只要像鸭子就是鸭子),Rust 的 Trait 是"名义类型"(必须显式声明)。在大型项目中,这两种设计哲学对代码重构和维护有什么不同影响?
本文代码示例:
关注公众号:极客老墨
更多 AI 应用开发、工程实践和效率工具分享,欢迎扫码关注。
