前面章节我们有介绍过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的类型,也就是i32trait Add<RHS = Self> { type Output; fn add(self, rhs: RHS) -> Self::Output;}
复制代码
当然我们可以手动指定类型:
#[derive(Debug)]struct Millimeters(u32);#[derive(Debug)]struct Meters(u32);
// 指定Millimeters在使用 + 运算符时,+ 右边的类型是Metersimpl 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实现Pilotimpl Pilot for Human { fn fly(&self) { println!("这是你的队长在讲话"); }}// 为Human实现Wizardimpl 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画美国
关注「码生笔谈」公众号,阅读更多最新章节
评论