Rust Trait
trait 定义共享行为;impl Trait for Type 提供实现;bound 约束泛型;与接口/类型类的对照。
#concept
#growing
#dev
#rust
[!info] related notes
Rust Trait
这个概念为什么出现
不同的类型需要同一套能力:可摘要、可序列化、可迭代、可比较。
Rust 不用继承树挂方法,而用 trait 声明行为,再为具体类型实现。
[!abstract] 一句话理解
trait是行为契约;impl Trait for Type提供实现;泛型上的T: Trait把契约变成编译期约束。
最小可运行示例
场景:通知中心——邮件与短信都能 send
示例用日志模拟两种通知渠道,trait 统一调用面;不会发送邮件或短信。真实 I/O 接口还需表达发送失败,而不能以 println 当送达证据。
// 业务意图:统一通知发送接口,屏蔽渠道差异。
// 教学点:trait 定义、为结构体实现、泛型 bound、&dyn Trait 点到为止。
trait Notifier {
fn send(&self, user_id: u64, body: &str);
}
struct EmailNotifier { from: String }
struct SmsNotifier { sender_id: String }
impl Notifier for EmailNotifier {
fn send(&self, user_id: u64, body: &str) {
println!("email from={} user={} body={}", self.from, user_id, body);
}
}
impl Notifier for SmsNotifier {
fn send(&self, user_id: u64, body: &str) {
println!("sms sender={} user={} body={}", self.sender_id, user_id, body);
}
}
// 静态分发:编译期单态化
fn notify_all<N: Notifier>(n: &N, user_id: u64, body: &str) {
n.send(user_id, body);
}
fn main() {
let email = EmailNotifier { from: String::from("noreply@shop") };
let sms = SmsNotifier { sender_id: String::from("SHOP") };
notify_all(&email, 7, "order shipped");
notify_all(&sms, 7, "order shipped");
}
建议运行:cargo run
期望输出:
email from=noreply@shop user=7 body=order shipped
sms sender=SHOP user=7 body=order shipped
结合场景再看三个关注点
- 调用方依赖 trait 而非具体渠道
- 泛型 bound 零成本静态分发
- 需要异构集合时再引入
dyn Notifier(进阶)
核心概念与准确模型
定义与实现
trait Summary {
fn summarize(&self) -> String;
fn prefix(&self) -> String { // 默认实现
String::from("(summary)")
}
}
派生与标准 trait
常见:Debug、Clone、Default、PartialEq、Iterator、From/Into。
约束写法
fn f<T: Clone + Debug>(x: T)where子句更清晰
静态分发 vs 动态分发
- 参数位置的
impl Trait/T: Trait通常静态分发;返回位置的 impl Trait 隐藏一个具体类型,并非运行时任选不同类型 - trait 对象:
&dyn Trait/Box<dyn Trait>通过虚表动态分发;trait 必须满足 dyn compatibility(旧称对象安全)。引用形式不要求堆分配 - 普通
T: Trait默认还要求 Sized;若要接收 dyn Trait 的借用,常需T: Trait + ?Sized
设计动机
- 组合行为,避免深层继承
- 孤儿规则等限制保证 trait 实现的一致性
- 与泛型一起支撑零成本抽象
边界与误区
- 常见约束是不能为外部类型直接实现外部 trait;完整孤儿规则还考虑 trait 参数中的本地类型及未覆盖类型参数,不能仅按 self 类型是否本地判断。需要适配时常用本地 newtype
- 默认方法可重写
- trait 对象方法接收者限制
[!warning] 常见误区:把 trait 当 Java interface 的 1:1 有默认方法、关联类型、泛型方法等更丰富机制;对象安全也不同。
工程实践
- 小而专的 trait 优于上帝接口
- 公共 API 用 trait 隐藏实现
- 泛型与 dyn 根据异构集合、编译体积、开放扩展和性能选择;泛型不保证所有场景更快
- 善用
From/TryFrom做转换契约
本节总结
- trait 定义能力
- impl 提供实现
- bound 约束泛型
- 静态/动态分发按需
自测题
T: Notifier与&dyn Notifier主要差别?- 为什么需要默认实现?
参考答案
- 前者单态化静态分发;后者运行时多态、有对象安全约束。
- 减少样板,同时保留可覆盖点。
延伸阅读与资料来源
| 资料 | 类型 | 支撑内容 |
|---|---|---|
| The Book — Traits | 官方书 | trait |
| Rust By Example — Traits | 官方 | 示例 |