trait
本文分两层:第一层是初学阶段需要的全部(读到"初学到此为止"即可);之后是完整展开,需要时再读。依据 The Book ch.10.2(中文)编写。
初学阶段
trait 是方法的打包单位:库作者把一批方法装进一个 trait,再把这个"方法包"挂到某些类型上,这些类型就拥有了这批方法。
以 rand 库(提供随机数功能)为例:它把 random_range 方法装进 RngExt 这个 trait,并挂到随机数生成器类型上。使用时:
use rand::RngExt; // 引入这个方法包
let n = rand::rng().random_range(1..=100); // 生成器随即可以调用该方法调用某 trait 定义的方法前,必须先用 use 引入该 trait。原因:不同库可能提供同名方法,显式引入可消除歧义。
初学到此为止。trait 的定义与实现是 Rust 的支柱概念,下面的展开内容在后续课程需要时再回来查。
完整展开
官方定义
trait 告诉 Rust 编译器某个特定类型拥有可能与其他类型共享的功能。可以通过 trait 以一种抽象的方式定义共享的行为。—— The Book ch.10.2
它类似于其他语言的接口(interface),但有一些差异。
定义与实现
定义 trait:声明方法签名,签名以分号结尾(不写方法体):
pub trait Summary {
fn summarize(&self) -> String;
}为类型实现:impl trait名 for 类型名,在方法体中写出该类型自己的行为:
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}实现之后,Tweet 的实例就可以调用 summarize,与非 trait 方法写法完全一致。
默认实现
trait 中的方法可以带默认方法体,实现者可以选择保留或覆盖:
pub trait Summary {
fn summarize_author(&self) -> String;
fn summarize(&self) -> String {
format!("(Read more from {}...)", self.summarize_author())
}
}实现者只需提供 summarize_author,即可免费获得 summarize 的功能。默认实现可以调用同一 trait 中的其他方法。
trait 作为参数(trait bound)
函数可以要求参数"实现了某个 trait",从而接受多种类型:
pub fn notify(item: impl Summary) { ... } // impl Trait 语法糖
pub fn notify<T: Summary>(item: T) { ... } // trait bound 语法两种写法的区别:<T: Summary> 可以强制多个参数是同一类型;+ 可叠加多个约束(如 Summary + Display);约束过多时用 where 从句保持签名整洁。返回值也可写 impl Summary,但只能对应一种具体类型。
孤儿规则(orphan rule)
只有当 trait 或类型至少之一定义于当前 crate 时,才能为该类型实现该 trait。例如不能在自己的 crate 里为标准库的 Vec<T> 实现标准库的 Display。该规则(相干性/coherence 的一部分)保证不同 crate 的实现不会互相冲突。
静态派发与动态派发
- 泛型 + trait bound:编译期单态化(为每个具体类型生成专用代码),零运行时开销。
dyn Trait(trait 对象,如Box<dyn Draw>):运行期通过虚表(vtable)间接调用,有少量开销,用于需要混合多种类型的场景。详见 The Book ch.17.2(中文)。
常用标准 trait
Debug({:?} 输出)、Display({} 输出)、Clone / Copy(复制语义)、PartialEq / Eq(相等判断)、Hash、Iterator、From / Into(类型转换)、Default(默认值)。其中多数可通过 #[derive(Debug, Clone, ...)] 自动生成,可派生的 trait 见 The Book 附录 C(中文)。
参见
- crate —— trait 来自某个具体的 crate(如标准库或 rand)