本节摘要:Trait 定义一组方法签名,类型通过 impl 履行契约;默认方法让契约自带通用实现。本节覆盖 trait 定义、实现规则(孤儿规则)、默认方法与标准库高频 Trait。读完你能为自己的类型接入整个标准库生态的协议体系。
trait Archivable { fn archive_id(&self) -> String; // 必须履行 fn summary(&self) -> String { // 默认条款 format!("[{}]", self.archive_id()) } } struct PaperCase { no: u32 } impl Archivable for PaperCase { fn archive_id(&self) -> String { format!("P-{}", self.no) } } let c = PaperCase { no: 173 }; println!("{}", c.summary()); // P-173,默认方法直接可用
两条司法限制:实现要么在类型本地、要么在 Trait 本地(孤儿规则),防止两个库给同一类型实现同一契约互相冲突;同类型对同 Trait 只能实现一次。
summary 只依赖契约内其他方法,实现方写一个 archive_id 就能白得一套流程。标准库的 Display、Iterator 大量用这招——Iterator 有七十多个默认方法,全部由你实现的那一个 next 推导出来。
| Trait | 契约内容 | 常见获得方式 |
|---|---|---|
| Debug | 调试打印 | 派生宏自动生成 |
| Clone 与 Copy | 显式复制、位复制 | 派生(Copy 需字段全 Copy) |
| PartialEq | 相等比较 | 派生 |
| Display | 用户可读输出 | 手工实现(一个 fmt 方法) |
| From | 从别的类型转换而来 | 手工实现,即获得 Into 反向能力 |
From 与 Into 是一对赠品判例:为 A 实现 From<B>,就自动得到 b.into() 得 A——标准库的 blanket impl 在起作用。
use std::fmt::Display; fn announce<T: Display>(item: T) { println!("现在宣布:{}", item); }
T: Display 是契约约束:只有会展示的类型能进来。约束机制在下一节全面展开。

trait 方法可以有默认实现,这给了"少量必须实现 + 多量可选覆盖"的接口设计模式,标准库的 Iterator 是最著名判例——只需实现 next,其余几十个方法全部免费。
trait Summarize { fn headline(&self) -> String; // 必须实现 fn preview(&self) -> String { // 默认实现 format!("[{}] {}", self.tag(), self.headline()) } fn tag(&self) -> String { // 默认实现可被覆盖,也可被其他默认方法调用 String::from("通告") } } struct Verdict { no: u32 } impl Summarize for Verdict { fn headline(&self) -> String { format!("第 {} 号判决", self.no) } fn tag(&self) -> String { String::from("判词") } // 覆盖默认 } fn main() { let v = Verdict { no: 173 }; println!("{}", v.preview()); // [判词] 第 173 号判决 }
注意默认方法之间可以互相调用,运行时分派到实现方的覆盖版——这是 trait 与早期接口最大的差异之一,也是 Iterator 组合子生态的根基。
trait Archiver { type Item; // 关联类型:实现时定死 fn store(&mut self, item: Self::Item); fn count(&self) -> usize; } struct Shelf { slots: Vec<String> } impl Archiver for Shelf { type Item = String; // 每个 Shelf 只有一种物品 fn store(&mut self, item: String) { self.slots.push(item); } fn count(&self) -> usize { self.slots.len() } }
关联类型与泛型参数的选择判据:一个类型对同一 trait 只应有一种实现方式时用关联类型(读代码不用带参数);确实需要多种实现(如 From 多目标转换)时才用泛型参数。标准库里 Iterator 的 Item、Deref 的 Target 都是关联类型的判例。
trait 或类型至少有一个定义在当前 crate,才能写 impl。这条"孤儿规则"防止两个包对同一对 (trait, type) 给出冲突实现。绕行手法是新类型包装:给外部类型套一层本地元组结构体,再为包装实现本地或外部 trait。代价是多一层 0 成本转换,换来的是全局 impl 的唯一性——这是 Rust 把"库组合不可预测冲突"这类悬案在编译期消灭的方式,第 7 章多 crate 工程会再遇到它。
| 可 derive | 提供什么 | 何时手写 |
|---|---|---|
| Debug / Clone / Copy | 调试打印、复制语义 | 自定义格式或含非 Copy 字段却要 Copy 时不可 |
| PartialEq / Eq | 值相等 | 相等含业务规则(浮点比较) |
| Hash | 哈希键资格 | 与 PartialEq 语义需一致 |
| Default | 零值构造 | 有意义的默认而非全零 |
derive 不是语法糖那么简单,它调用的是第 9 章的过程宏——编译期读入 token 流、生成 impl 块再交回编译器。理解这一点,derive 与手写 impl 完全等价的认知就成立了。