在之前的章节中,我们的代码中出现了一些 trait 的用法,但是并没有展开来讲。本章我们将集中介绍 trait 的所有高级用法。
Placeholder Type
再来看看之前的 Iterator 定义:
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
这里的 type Item
就是一个 placeholder type。它存在的意义是让这个 trait 的定义中的其他函数签名可以使用 Item
作为类型的 placeholder。而当实现 Iterator
这个 trait 时,用实际的类型去替换这个 placeholder。
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
// --snip--
这听起来非常像泛型。那么为什么不写成下面的形式呢:
pub trait Iterator<T> {
fn next(&mut self) -> Option<T>;
}
这主要是为了保证对于一个类型,只有一个 Iterator
的实现。如果用泛型,那么就可以实现多个,例如Iterator<u32>
,Iterator<String>
。这样在使用的时候,也必须指明类型,对于 Iterator,我们显然只需要一种实现。
默认泛型参数和操作符重载
为泛型指定默认类型的语法是:
<PlaceholderType=ConcreteType>
一个经典用例是操作符重载。Rust 不允许任意创建或者重载操作符,只能通过重载 std::ops
中定义的 trait 来实现。例如,我们可以重载对 Point
的加操作:
use std::ops::Add;
// Debug for "{:?}", PartialEq for "=="
#[derive(Debug, PartialEq)]
struct Point {
x: i32,
y: i32,
}
impl Add for Point {
type Output = Point;
fn add(self, other: Point) -> Point {
Point {
x: self.x + other.x,
y: self.y + other.y,
}
}
}
fn main() {
assert_eq!(
Point { x: 1, y: 0 } + Point { x: 2, y: 3 },
Point { x: 3, y: 3 }
);
}
上面代码中我们实现了 std::ops::Add
trait:
trait Add<Rhs=Self> {
type Output;
fn add(self, rhs: Rhs) -> Self::Output;
}
这个 trait 是含有一个默认参数Rhs
的,使用它可以允许不同类型相加,这也是为什么它需要一个 placeholder type Output
,否则返回值不知道应该选哪个类型了。
下面的代码实现了不同类型间的 Add
:
use std::ops::Add;
struct Meters(u32);
#[derive(Debug, PartialEq)]
struct Millimeters(u32);
impl Add<Meters> for Millimeters {
type Output = Millimeters;
fn add(self, other: Meters) -> Millimeters {
Millimeters(self.0 + (other.0 * 1000))
}
}
fn main() {
assert_eq!(Millimeters(500) + Meters(1), Millimeters(1500));
}
因此,默认参数的主要作用是:
- 允许扩展一个类型,但是不破坏现有的代码
- 允许用户自定义一些极少会用到的行为
调用不同 trait 的同名方法
Rust 允许不同的 trait 用同样的方法名。因此,对于同一个对象,如何区分调用哪个 trait 的方法呢?
trait Pilot {
fn fly(&self);
}
trait Wizard {
fn fly(&self);
}
struct Human;
impl Pilot for Human {
fn fly(&self) {
println!("This is your captain speaking");
}
}
impl Wizard for Human {
fn fly(&self) {
println!("Up!");
}
}
impl Human {
fn fly(&self) {
println!("*waving arms furiously*");
}
}
fn main() {
let person = Human;
person.fly();
// specify trait name before method to disambiguate
Pilot::fly(&person);
Wizard::fly(&person);
}
可以通过明确指出 trait 名区分:
person.fly();
// specify trait name before method to disambiguate
Pilot::fly(&person);
Wizard::fly(&person);
使用 supertrait 表明 trait 的依赖关系
有时,实现某个 trait A 需要使用另一个 trait B。这时候,trait B 就称为 supertrait。我们可以用下面形式表明他们的关系:
trait A: B {
// snip
这样,如果某个类只实现了 A
但是没有实现 B
,编译时就会报错。
使用 newtype pattern 实现 trait
回忆一下之前讲过的“孤儿准则”:
如果 type 和 trait 都不是在当前 crate 定义的,那不允许为这个 type 实现这个 trait。
实际上,我们可以通过 newtype pattern 绕过这个检查,即,将这个 type 封装在一个 tuple struct 里。
例如我们希望自己给 Vec<T>
类型实现 Display
trait,根据孤儿准则,由于 Vec<T>
类型和 Display
trait 都是在其他 crate 定义的,因此无法直接实现。我们就将 Vec<T>
包装成另一个类型绕过检查:
struct Wrapper(Vec<String>);
代码为:
use std::fmt;
struct Wrapper(Vec<String>);
impl fmt::Display for Wrapper {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "[{}]", self.0.join(", "))
}
}
fn main() {
let w = Wrapper(vec![String::from("hello"), String::from("world")]);
println!("w = {}", w);
}
newtype pattern 还能实现更多的 trait,例如 Deref
,来直接返回内部的数据 &self.0
。