本章要回答的三个问题:不同类型之间的共同行为契约怎么立?一份代码怎么安全地服务多种类型?编译期多态与运行期多态各在什么案子里用?
第 4 章建好了数据卷宗,但司法系统里还有"判例法":不管案卷是纸质还是电子,都要能"归档"——这是行为契约,即 Trait。而书记员写一次归档流程应当适用于所有案卷类型——这是泛型。两者结合(Trait 约束泛型)构成 Rust 的抽象主力,且抽象在编译期展开,零运行时成本。
| 节 | 回答哪个问题 | 关键产出 |
|---|---|---|
| 5.1 Trait 定义与实现 | 契约怎么立、怎么履行 | 默认方法、标准库常用 Trait |
| 5.2 泛型 | 一份代码多种类型 | 单态化、Trait 约束三写法 |
| 5.3 Trait 对象 | 运行期多态怎么做 | dyn 与泛型对比表、对象安全 |
Trait 是后面各章的地基:第 6 章错误处理靠 From 与 Display,第 8 章并发安全靠 Send 与 Sync,第 9 章派生宏自动生成 Trait 实现。
trait 与泛型的报错集中在"约束不满足"与"不兼容"两条线上。
| 报错 | 案情 | 修法方向 |
|---|---|---|
| E0277 | 类型缺 trait 实现 | 补 impl、换类型或加约束 |
| E0038 | trait 不能做 dyn 对象 | 去 Self 返回、泛型方法加 Sized 豁免 |
| E0308 + 泛型 | T 绑定到两个不同类型 | 统一类型或 turbofish |
| E0210 | 违反孤儿规则 | 新类型包装后再 impl |
| 判据 | 静态派发(泛型) | 动态派发(dyn) |
|---|---|---|
| 类型成员 | 编译期已知 | 运行期生长 |
| 调用成本 | 可内联,零间接 | 一次 vtable 寻址 |
| 代码体积 | 每类型一份,膨胀 | 一份 |
| 二进制边界 | 跨边界需单态化 | 天然适用 |
| 典型席位 | 算法、迭代器 | 插件、事件总线、异构集合 |
工程默认值是泛型;只有在"需要把不同类型装进同一容器"或"编译边界不许泄漏泛型"时退到 dyn。两者混用也常见——对内泛型求性能,对外 dyn 求稳定接口。
trait 体系里有一类特殊判例尚未开庭:那些表达"成功或失败"的 trait 与类型——Result 就是标准库中最著名的 enum,? 运算符则是对 From trait 的一次语法级引用。第 6 章错误处理庭实质上是本章法条在可靠性辖区的适用,读的时候留意 From 与 ? 的联动即可。
能用泛型不写 dyn,能用关联类型不堆参数,能收窄约束不放宽。三个"能"字背后是同一价值排序:编译期信息越足,代码越快越好查。反向的三个信号——异构集合、运行期注册、二进制边界求稳——出现任意一条,才把具体类型换 Box<dyn Trait>。口诀配前文的选型卡,本章的工程决策就闭环了。
一问:默认方法能调用同 trait 的另一个方法吗?答:能,且运行时分派到实现方版本,Iterator 生态由此筑成。二问:fn f(x: &dyn Trait) 与 fn f<T: Trait>(x: &T) 的机器码差异?答:前者一份、经 vtable 间接调用;后者每类型一份、可内联。三问:孤儿规则禁止什么?答:为外部类型实现外部 trait,防止冲突 impl;解法是新类型包装。三题过线,trait 体系就从语法升级为设计工具。