The Rust Programming Language - 第19章 高级特征 - 19.2 高级trait

柳轶
2023-12-01

19 高级特征

我们将在这一章学习更多高级功能

19.2 高级trait

关联类型在trait定义中指定占位符类型

关联类型是一个方式,是一个将类型占位符与trait相关联,这样在trait 的方法签名中就可以使用这些占位符类型

pub trait Iterator {
    type Item; //使用type关键字将占位类型Item与Iterator关联起来

    fn next(&mut self)->Option<Self::Item>;//在方法签名中使用这些占位符类型
}

trait实现者会针对特定的实现在这个类型的位置指定相应具体类型(也就是用的时候才会明确)

关联类型与泛型有点相似,但它有什么新特点呢

impl Iterator for Counter {
    type Item = u32;

    fn next(&mut self) -> Option<Self::Item> {
        // -- snip --
    }
}

我们为Counter实现了trait,注意:实现的时候要指定Item具体的类型,这里是u32

那如果使用泛型来执行上述过程呢?

pub trait Iterator<T> {
    fn next(&mut self)->Option<T>;
}

使用泛型Iterator trait假想定义

为Counter实现使用泛型的Iterator,针对每一个具体类型都得写一个实现,但是使用关联类型的话,只需要指定一下Item的具体类型就可,所以使用泛型不是不可以,只是比较麻烦而已

impl Iterator<String> for Counter {
    // -- snip --
}

impl Iterator<u32> for Counter {
    // -- snip --
}

impl Iterator<bool> for Counter {
    // -- snip --
}

默认泛型类型参数和运算符重载

使用泛型类型参数时,可以为泛型指定一个默认的具体类型。如果默认类型足够的话,就不用为具体类型实现trait了

泛型类型指定默认类型的语法时在声明泛型类型时使用<>PlaceholderType=ConcreteType>

这种情况一个非常好的例子是用于运算符重载

Rust并不支持自定义运算符或重载任意运算符,不过std::ops中列出的运算符和相应的trait可以实现类似的功能,比如下面的例子中,我们把两个结构体相加

use std::{ops::Add, process::Output};

#[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:3} + Point{x:2,y:3},Point{x:3,y:6})
}
trait Add(RHS=Self){
    type Output;

    fn add(self,rhs:RHS)->Self::Output;
}

在Add trait中的默认泛型,RHS是一个泛型类型参数,如果为具体类型实现add trait时不指定关联类型的具体类型,那就默认使用Self类型

我们再来看一个不使用默认类型的例子

struct Millimeters(u32);
struct Meters(u32);
impl Add<Meters> for Millimeters {
    type Output = Millimeters;

    fn add(self,other:Meters) -> Millimeters {
        Millimeters(self.0 + (other.0 * 1000))
    }
}

小结:默认参数类型主要用于:

扩展类型而不破坏原有代码

在特定情况下自定义

完全限定语法与消歧义:调用相同名称的方法

Rust的trait可以同名,也可为一个类型同时实现多个trait。所以调用这些同名方法需要明确告知Rust我们要用哪一个

我们直接来看例子

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();
}

直接打印,默认调用类型上实现的方法

     Running `target/debug/advancedfunction`
*waving arms furiously*
fn main(){
    let person = Human;
    Pilot::fly(&person);
    Wizard::fly(&person);
    person.fly();
}
  Running `target/debug/advancedfunction`
This is your captain speaking.
Up!
*waving arms furiously*

其他的fly实现需要具体明确指定

然而关联函数是trait的一部分,但没有self参数

当统一作用域的两个类型实现了同一个trait,Rust就不能计算出我们期望的是哪一个类型,除非使用完全限定语法。例子如下

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")
    }
}

fn main(){
    println!("A baby dog is called a {}",Dog::baby_name());
}

     Running `target/debug/advancedfunction`
A baby dog is called a Spot

这不是我们想要调用的方法,我们希望调用的是Dog上Animal trait实现的那部分的baby_name函数,这样能够打印出A baby dog is called a Puppy

fn main(){
    println!("A baby dog is called a {}",Animal::baby_name());
}
error[E0283]: type annotations needed
  --> src/main.rs:19:42
   |
19 |     println!("A baby dog is called a {}",Animal::baby_name());
   |                                          ^^^^^^^^^^^^^^^^^ cannot infer type
   |
   = note: cannot satisfy `_: Animal`

我们尝试调用Animal trait的baby_name函数,但是发生了编译错误。因为Animal::baby_name是关联函数而不是方法,它没有self参数。为了消除歧义,我们得使用完全限定语法

fn main(){
    println!("A baby dog is called a {}",<Dog as Animal>::baby_name());
}
     Running `target/debug/advancedfunction`
A baby dog is called a Puppy

在尖括号中我们提供了类型注解,将Dog作为Animal对待,来指定希望调用的是Dog上Animal trait实现中的baby_name函数

通常,完全限定语法定义为:

Type as Trait>::function(receiver_if_method,next_arg,...);

对于关联函数,其没有一个receiver,故只会有其他参数的列表。可以选择在任何函数或者方法调用处使用完全限定语法。然而,只有当存在多个同名实现而Rust需要帮助以便我们知道调用哪个实现时,才需要使用这个较为冗长的语法

父trait用于在另一个trait中使用某trait的功能

有时候我们需要在一个trait中使用另一个trait的功能,那这个被使用的trait就是我们实现trait的父(超)trait

我们来直接看例子:我们现在要创建一个带有outline_print方法的trait OutlinePrint,打印出如下带有*号框的值

**********
*        *
* (1, 3) *
*        *
**********
trait OutlinePrint: fmt::Display {
    fn outline_print(&self) {
        let output = self.to_string();
        let len = output.len();
        println!("{}","*".repeat(len+4));
        println!("*{}*","".repeat(len+2));
        println!("* {} *",output);
        println!("{}","*".repeat(len+4));
    }
}

我们定义了一个我们所期望的trait,但是呢,他希望使用Display trait的功能。所以类型必须实现Display trait,这样我们就可以使用to_string了

struct Point {
    x: i32,
    y: i32,
}

impl OutlinePrint for Point {}
error[E0277]: the trait bound `Point: std::fmt::Display` is not satisfied
  --> src/main.rs:20:6
   |
20 | impl OutlinePrint for Point {}
   |      ^^^^^^^^^^^^ `Point` cannot be formatted with the default formatter;
try using `:?` instead if you are using a format string
   |
   = help: the trait `std::fmt::Display` is not implemented for `Point`

如果类型没有实现Display,那么为它实现OutlinePrint会发生如上错误

struct Point {
    x:i32,
    y:i32,
}

impl fmt::Display for Point{
    fn fmt(&self,f:&mut fmt::Formatter) -> fmt::Result{
        write!(f,"{},{}",self.x,self.y)
    }
}

如果为类型实现Display trait,则可以成功编译

newtype 模式用以在外部类型上实现外部trait

在第十章,我们提到了孤儿规则,说明只要trait或类型对于当前crate是本地的话就可以在此类型上实现trait

但是newtype可以绕过这一规则,它需要在一个元组结构体中创建一个新类型。元组结构体带有一个字段,它是一个希望实现trait的类型的简单封装,这个封装对于crate来说是本地的,这样就可以在这个封装上实现trait

例如:想要在Vec 上实现Display,孤儿规则不允许。因为Display trait 和 Vec 都定义于我们的crate之外

但是可以创建一个包含Vec 实例的Wrapper 结构体,接着可以在Wrapper上实现Display 并使用Vec 的值

use std::fmt::{self, write};

struct Wrapper(Vec<String>);

impl fmt::Display for Wrapper {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "[{}]", self.0.join(","))
    }
}

Display的实现使用self.0来访问其内部的Vec, 因为Wrapper 是元组结构体而Vec是结构体总位于索引0的项。接着就可以使用Wrapper中的Display的功能了

这个方法的缺点是,因为Wrapper是一个新类型,它没有定义于之上的方法,必须直接在Wrapper上实现Vec的所有方法,这样就可以代理到self.0上。这就允许我们完全像Vec那样对待Wrapper

如果希望新类型拥有其内部类型的每一个方法,为封装类型实现Deref trait并返回内部类型是一种解决方法。如果不希望,如为了限制封装类型的行为,则必须只自行实现所需的方法

上面便是new type模式如何于trait结合使用的,还有一个不涉及trait的实用模式

 类似资料: