前面章节我们有介绍过trait
像“接口”一样,讲过了简单的使用方式,这一节我们看下trait
的一些相关高级特性
在 trait 的定义中使用关联类型指定占位类型
关联类型(associated type)是trait
中的类型占位符,它可以被用于trait
的方法签名中,例如Iterator trait
中就含有关联类型Item
:
trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
复制代码
使用这个关联类型的Iterator trait
:
struct Counter {
value: u32
}
impl Iterator for Counter {
type Item = u32; // 定义Item类型
fn next(&mut self) -> Option<Self::Item> { // 返回值为Option<Self::Item>
if self.value < 3 {
self.value += 1;
Some(self.value)
} else {
None
}
}
}
let mut counter = Counter {
value: 0
};
println!("{:?}", counter.next()); // Some(1)
println!("{:?}", counter.next()); // Some(2)
println!("{:?}", counter.next()); // Some(3)
println!("{:?}", counter.next()); // None
复制代码
有同学可能在想,为什么不用泛型来实现呢,比如这样使用泛型实现Iterator
:
trait Iterator<T> { // 定义泛型参数
fn next(&mut self) -> Option<T>;
}
// 使用u32类型
impl Iterator<u32> for Counter {
fn next(&mut self) -> Option<u32> {
if self.value < 3 {
self.value += 1;
Some(self.value)
} else {
None
}
}
}
println!("{:?}", counter.next()); // Some(1)
println!("{:?}", counter.next()); // Some(2)
println!("{:?}", counter.next()); // Some(3)
println!("{:?}", counter.next()); // None
复制代码
上面使用关联类型和泛型都可以实现Iterator trait
,但是为什么要设计一种关联类型,而不用泛型呢?
试想,既然利用泛型可以实现实现u32
类型的迭代器:impl Iterator<u32> for Counter
,那么就可以也实现其他类型的迭代impl Iterator<String> for Counter
,那么 next 方法中每次返回的就是一个String
,如果我们为Counter
同时实现了这两种类型的迭代器,那么在for
循环的时候,应该使用哪个类型的next
方法呢?为了避免这种冲突,所以只能使用关联类型来实现Iterator trait
,进而阻止单个类型多次实现同一个trait
的行为。
运算符重载
rust 中的很多运算符是可以重载的,这里以+
为例,我们可以自定义实现+
的行为,下面为结构体上实现Add trait
来重载+
运算符,实现结构体相加的功能:
use std::ops::Add;
#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
// 为Point重载 + 运算符的行为
impl Add for Point {
// 定义关联类型Output为add的返回值
type Output = Point;
fn add(self, other: Point) -> Point {
Point {
x: self.x + other.x,
y: self.y + other.y,
}
}
}
println!("{:?}", Point { x: 1, y: 0 } + Point { x: 2, y: 3 });
// Point { x: 3, y: 3 }
复制代码
根据上面的例子我们可以顺便看下Add trait
:
// 注意定义了默认的RHS类型是自身,也就是上面实现了Add的类型
// 比如:1 + 2 RHS就是1的类型,也就是i32
trait Add<RHS = Self> {
type Output;
fn add(self, rhs: RHS) -> Self::Output;
}
复制代码
当然我们可以手动指定类型:
#[derive(Debug)]
struct Millimeters(u32);
#[derive(Debug)]
struct Meters(u32);
// 指定Millimeters在使用 + 运算符时,+ 右边的类型是Meters
impl Add<Meters> for Millimeters {
type Output = Millimeters;
fn add(self, other: Meters) -> Millimeters {
Millimeters(self.0 + (other.0 * 1000))
}
}
println!("{:?}", Millimeters(1) + Meters(2));
// Millimeters(2001)
复制代码
用于消除歧义的完全限定语法:
当我们为一个结构体实现多个trait
时,难保这些trait
中有重名的方法,而且结构体中也可能已经存在相同的方法:
struct Human;
impl Human {
// Human结构体自身的方法
fn fly(&self) {
println!("挥舞着手臂");
}
}
trait Pilot {
// Pilot trait也具有fly方法
fn fly(&self);
}
trait Wizard {
// Wizard trait也具有fly方法
fn fly(&self);
}
// 为Human实现Pilot
impl Pilot for Human {
fn fly(&self) {
println!("这是你的队长在讲话");
}
}
// 为Human实现Wizard
impl Wizard for Human {
fn fly(&self) {
println!("起飞!");
}
}
let person = Human;
// 默认会调用Human自身实现的方法
person.fly(); // 挥舞着手臂
复制代码
手动指定明确调用的方法,有点像 JS 中函数的call
方法:
Human::fly(&person); // 挥舞着手臂
Pilot::fly(&person); // 这是你的队长在讲话
Wizard::fly(&person); // 起飞!
复制代码
关联函数是trait
的一部分,但没有self
参数,可以理解为 JS 中class
的静态方法(也就是类上的方法,而非prototype
上的方法,比如Object.create()
)。当同一作用域的两个类型实现了同一trait
,rust 就不能计算出我们期望使用的是哪一个,除非使用 完全限定语法(fully qualified syntax):
trait Animal {
fn baby_name() -> String;
}
struct Dog;
impl Dog {
fn baby_name() -> String {
String::from("Spot")
}
}
impl Animal for Dog {
fn baby_name() -> String {
String::from("puppy")
}
}
// 默认仍然会调用Dog自身实现的baby_name方法
println!("小狗的名字叫 {}", Dog::baby_name());
// 小狗的名字叫 Spot
println!("小狗的名字叫 {}", Animal::baby_name());
// 报错, 由于Animal::baby_name是一个没有self参数的关联函数而不是方法,
// 所以Rust无法推断出我们想要调用哪一个Animal::baby_name的实现
// 使用完全限定语法解决
println!("小狗的名字叫 {}", <Dog as Animal>::baby_name());
// 小狗的名字叫 puppy
复制代码
用于在 trait 中附带另外一个 trait 功能的超 trait
有时在一个A trait
使用B trait
的功能。在这种情况下,我们需要使当前A trait
的功能依赖于B trait
同时被实现,这个被依赖的B trait
也就是当前A trait
的超trait
(supertrait):
#[derive(Debug)]
struct Position(u32, u32);
trait Logger: std::fmt::Debug { // Logger的实现依赖于Debug的实现
fn log(&self) {
println!("position: {:?}", self);
}
}
impl Logger for Position {} // 报错,Logger没有实现Debug
复制代码
简单的为Logger
实现Debug
,这里利用派生:
#[derive(Debug)]
struct Position(u32, u32);
trait Logger: std::fmt::Debug {
fn log(&self) {
println!("position: {:?}", self);
}
}
impl Logger for Position {}
let position = Position(1, 2);
position.log(); // position: Position(1, 2)
复制代码
使用 newtype 模式在外部类型上实现外部 trait
前面有说过trait
实现的限制:只有当类型和对应trait
中的任意一个定义在本地包内时(孤儿规则),我们才能够为该类型实现这一trait
,实际上可以使用newtype
模式来巧妙地绕过这个限制,下面尝试为Vec<String>
实现Display trait
:
impl std::fmt::Display for Vec<String> {};
// 报错,因为Display和Vec都是外部定义的
复制代码
创建Wrapper
类型封装Vec<String>
以便能够实现Display trait
:
impl std::fmt::Display for Wrapper {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "[{}]", self.0.join(", ")) // 访问self.0,也就是Vec<String>来使用Display
}
}
let w = Wrapper(vec![String::from("hello"), String::from("world")]);
println!("w = {}", w); // w = [hello, world]
复制代码
封面图:跟着Tina画美国
关注「码生笔谈」公众号,阅读更多最新章节
评论