高级Trait
1. 关联类型(Associated Types)
一个 trait 通常需要与某个类型相关联。例如迭代器的 next 方法要返回某种类型的值,这个类型是什么应该由实现者决定。关联类型允许 trait 为实现者定义一个“类型占位符”。
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
实现:
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
Some(self.count)
}
}
为什么需要关联类型,而不是使用泛型?
如果使用泛型参数版本的 trait:
pub trait Iterator<T> {
fn next(&mut self) -> Option<T>;
}
那么:
- 同一个类型可以多次为不同的 T 实现同一个 trait。
- Rust 会认为这些是不同的 trait 实现。
- 在调用时需要显式指定类型参数,否则无法推断。
例如:
let n: Option<u32> = Iterator::<u32>::next(&mut counter);
显然非常不便。
关联类型的意义:
- 每个类型对该 trait 只能有 一个 实现。
- 调用时无需额外标注类型,实现更清晰、更自然。
2. 默认类型参数(Default Type Parameters)
某些 trait 可以让泛型参数有默认值。标准库中的运算符重载 trait Add 就是典型例子:
trait Add<RHS=Self> {
type Output;
fn add(self, rhs: RHS) -> Self::Output;
}
其中RHS是泛型,如果不特殊指定,他默认就是Self(实现了这个trait的那个类型)。
示例:
impl Add for Point {
type Output = Point;
// 这里的other是RHS类型,默认就是Point
fn add(self, other: Point) -> Point {
Point { x: self.x + other.x, y: self.y + other.y }
}
}
3. 同名方法的冲突与调用方式
当一个类型实现了多个 trait,而这些 trait 中有同名方法时,会发生名称冲突。例如:
trait Pilot { fn fly(&self); }
trait Wizard { fn fly(&self); }
impl Pilot for Human { fn fly(&self) {...} }
impl Wizard for Human { fn fly(&self) {...} }
impl Human { fn fly(&self) {...} }
在调用时:
let chr = Human {
name: "chr",
};
chr.fly();
优先调用类型自身的实现(impl Human)。
若要显式调用 trait 上的方法:
Pilot::fly(&chr);
Wizard::fly(&chr);
这是 Rust 的显式消歧义规则,用以解决多重 trait 实现带来的方法名冲突。
4. 父trait(Trait Bounds on Traits)
有时一个 trait 的功能依赖另一个 trait 的功能。例如下面的 OutlinePrint:
trait OutlinePrint: fmt::Display {
fn outline_print(&self) {
let output = self.to_string();
...
}
}
OutlinePrint: Display 的含义是:
- 任何实现 OutlinePrint 的类型,必须先实现 Display。
- 因此 OutlinePrint 的默认实现中就可以安全地使用 to_string()。
5. newtype 模式与孤儿规则(Orphan Rule)
Rust 有“孤儿原则”:不能为外部类型实现外部 trait。例如:
- Vec<String> 是标准库类型;
- Display 是标准库 trait;
- 你不能写 impl Display for Vec<String>。
解决方法是 newtype 模式,创建一个本地类型包裹外部类型:
struct Wrapper(Vec<String>);
然后就可以安全地实现外部 trait:
impl fmt::Display for Wrapper {
...
}