写点什么

5 分钟速读之 Rust 权威指南(四十)高级 trait

用户头像
码生笔谈
关注
发布于: 2 小时前
5分钟速读之Rust权威指南(四十)高级trait

前面章节我们有介绍过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画美国


关注「码生笔谈」公众号,阅读更多最新章节

发布于: 2 小时前阅读数: 3
用户头像

码生笔谈

关注

欢迎关注「码生笔谈」公众号 2018.09.09 加入

前端、Rust干货分享,一起成为更好的Javascripter & Rustacean

评论

发布
暂无评论
5分钟速读之Rust权威指南(四十)高级trait