Rust Trait 的使用

trait(特征)被用来向rust编译器描述某些特定类型拥有的且能够被其他类型共享的功能,它使我们可以以一种抽象的方式来定义共享行为。我们还可以使用trait约束来将泛型参数指定为实现了某些特定行为的类型。

定义trait

定义一个trait:

pub trait Summary {
    fn summarize(&self) -> String;
}

在trait中定义方法签名,可以定义多个方法签名。

为类型实现trait

#![allow(unused_variables)]
fn main() {
	pub trait Summary {
	    fn summarize(&self) -> String;
	}
	
	pub struct NewsArticle {
	    pub headline: String,
	    pub location: String,
	    pub author: String,
	    pub content: String,
	}
	
	impl Summary for NewsArticle {
	    fn summarize(&self) -> String {
	        format!("{}, by {} ({})", self.headline, self.author, self.location)
	    }
	}
	
	pub struct Tweet {
	    pub username: String,
	    pub content: String,
	    pub reply: bool,
	    pub retweet: bool,
	}
	
	impl Summary for Tweet {//这里这里这里这里这里这里这里这里这里这里
	    fn summarize(&self) -> String {
	        format!("{}: {}", self.username, self.content)
	    }
	}
}

实现trait需要在impl关键字后提供我们想要实现的trait名,并紧接这for关键字及当前的类型名。在impl代码块中,我们同样需要填入trait中的方法,在花括号中实现针对该结构体的函数体。
如何我们便可以基于实例调用该trait的方法了:

use chapter10::{self, Summary, Tweet};

fn main() {
    let tweet = Tweet {
        username: String::from("horse_ebooks"),
        content: String::from(
            "of course, as you probably already know, people",
        ),
        reply: false,
        retweet: false,
    };

    println!("1 new tweet: {}", tweet.summarize());
}

trait有一个限制:只有当trait或类型定义于我们的库中时,我们才能为该类型实现对应的trait。
我们不能为外部类实现外部trait,这个限制被称为孤儿规则,因为他的父类型没有定义在当前库中。这一规则也是程序一致性的组成部分,确保了其他所有的人所编写的内容不会破坏到你的代码。如果没有这个规则,那么两个库可以分别对相同的类型实现相同的trait,rust无法确定使用哪一个版本。

默认实现

trait可以提供一个默认的实现,这样使用trait的类型就不用非要自己写实现了:

impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

语法上就是把签名的分号去掉,任何在花括号中写上函数体即可。

如果我们决定使用默认实现,而不自定义实现,那么就可以指定一个空的impl代码块:

impl Summary for NewsArticle {}

更骚的操作:
我们可以在默认实现中调用其他默认实现,哪怕这个实现里面是空的。在这个空的默认实现被实例实现后,trait又能有更强的多样性。

#![allow(unused_variables)]
fn main() {
	pub trait Summary {
	    fn summarize_author(&self) -> String;
	
	    fn summarize(&self) -> String {
	        format!("(Read more from {}...)", self.summarize_author())
	    }
	}
	
	pub struct Tweet {
	    pub username: String,
	    pub content: String,
	    pub reply: bool,
	    pub retweet: bool,
	}
	
	impl Summary for Tweet {
	    fn summarize_author(&self) -> String {
	        format!("@{}", self.username)
	    }
	}
}

使用trait作为参数

将trait作为参数,可以使实现了trait的类型传递进来。没有实现该trait的类型则不行。

pub fn notify(item: &impl Summary) {
    println!("Breaking news! {}", item.summarize());
}

你可能发现了,trait作为参数使用了泛型。

trait约束

上面的代码其实是trait约束的一种语法糖,它的完整形式其实是下面这样:

pub fn notify<T: Summary>(item: &T) {
    println!("Breaking news! {}", item.summarize());
}

有一种情况是一定要用完整的trait约束,而不能使用语法糖的,举个例子:
我们要使用两个trait参数

pub fn notify(item1: &impl Summary, item2: &impl Summary) {

这样写,item1和item2可以是两种不同的类型,如果你一定要item1和item2是相同的类型,那么只能用完整的约束:

pub fn notify<T: Summary>(item1: &T, item2: &T) {

通过+语法来指定多个trait约束

类型可以实现多个trait。传参的时候也可以规定传进来的类型需要实现多个trait,那么我们可以使用+语法来指定多个trait约束。

语法糖写法:

pub fn notify(item: &(impl Summary + Display)) {

完整trait约束写法:

pub fn notify<T: Summary + Display>(item: &T) {

使用where从句来简化trait约束

实现多个trait:

fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {

太多的trait会使函数签名臃肿且难以理解,所以加入where子句来整理trait约束:

fn some_function<T, U>(t: &T, u: &U) -> i32
    where T: Display + Clone,
          U: Clone + Debug
{

返回实现了trait的类型

返回值中同样可以返回trait:

fn returns_summarizable() -> impl Summary {
    Tweet {
        username: String::from("horse_ebooks"),
        content: String::from(
            "of course, as you probably already know, people",
        ),
        reply: false,
        retweet: false,
    }
}

但是,下面这种代码依旧不能成功编译,NewsArticle和Tweet都实现了impl Summary,却依然无法通过编译:

pub trait Summary {
    fn summarize(&self) -> String;
}

pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
}

pub struct Tweet {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub retweet: bool,
}

impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

fn returns_summarizable(switch: bool) -> impl Summary {
    if switch {
        NewsArticle {
            headline: String::from(
                "Penguins win the Stanley Cup Championship!",
            ),
            location: String::from("Pittsburgh, PA, USA"),
            author: String::from("Iceburgh"),
            content: String::from(
                "The Pittsburgh Penguins once again are the best \
                 hockey team in the NHL.",
            ),
        }
    } else {
        Tweet {
            username: String::from("horse_ebooks"),
            content: String::from(
                "of course, as you probably already know, people",
            ),
            reply: false,
            retweet: false,
        }
    }
}

碍于impl Trait工作方式的限制,rust不支持这种代码。之后会讨论。
(方法返回的类型不能是self,不能包含任何泛型参数,不然对象不安全。单态化使得trait忘记了自己self的具体类型)

额外:返回trait类型的引用

返回trait类型和返回trait类型的引用是不同的:
&(impl tra)

impl test{
    fn ttt(&self) -> &(impl tra){
        return self;
    }
}

使用trait约束来修复largest函数

在上一章中泛型数据类型,我们提到std::cmp::PartialOrd,现在让我们来修复这个错误。

无非就是让传进来的参数是实现了PartialOrd的类型,改一下函数签名就可以了:

fn largest<T: PartialOrd>(list: &[T]) -> T {//改一下函数签名
    let mut largest = list[0];

    for &item in list {
        if item > largest {
            largest = item;
        }
    }

    largest
}

fn main() {
    let number_list = vec![34, 50, 25, 100, 65];

    let result = largest(&number_list);
    println!("The largest number is {}", result);

    let char_list = vec!['y', 'm', 'a', 'q'];

    let result = largest(&char_list);
    println!("The largest char is {}", result);
}

}

但是不行,会报错:

$ cargo run
   Compiling chapter10 v0.1.0 (file:///projects/chapter10)
error[E0508]: cannot move out of type `[T]`, a non-copy slice
 --> src/main.rs:2:23
  |
2 |     let mut largest = list[0];
  |                       ^^^^^^^
  |                       |
  |                       cannot move out of here
  |                       move occurs because `list[_]` has type `T`, which does not implement the `Copy` trait
  |                       help: consider borrowing here: `&list[0]`

error[E0507]: cannot move out of a shared reference
 --> src/main.rs:4:18
  |
4 |     for &item in list {
  |         -----    ^^^^
  |         ||
  |         |data moved here
  |         |move occurs because `item` has type `T`, which does not implement the `Copy` trait
  |         help: consider removing the `&`: `item`

error: aborting due to 2 previous errors

Some errors have detailed explanations: E0507, E0508.
For more information about an error, try `rustc --explain E0507`.
error: could not compile `chapter10`.

To learn more, run the command again with --verbose.

大概就是说 cannot move out of type T。
原因在于我们写了:let mut largest = list[0];
这一句在没有实现Copy trait的类型运行时会发生move。
但是我们无法将list[0]中的值一处并绑定到largest变量上。(比如String)

那么我们加上一个Copy trait呗,缩小传入参数的范围:

fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {//改一下函数签名
    let mut largest = list[0];

    for &item in list {
        if item > largest {
            largest = item;
        }
    }

    largest
}

fn main() {
    let number_list = vec![34, 50, 25, 100, 65];

    let result = largest(&number_list);
    println!("The largest number is {}", result);

    let char_list = vec!['y', 'm', 'a', 'q'];

    let result = largest(&char_list);
    println!("The largest char is {}", result);
}

当然如果你如果想支持更多的数据类型,可以把Copy trait改成Clone trait。

使用trait约束来有条件地实现方法

rust支持对一个类型写多个impl代码块,所以我们可以单独为实现了指定trait的类型写方法。
比如如下代码,只有T实现了PartialOrd和Display这两个trait时才能实现cmd_display方法:

#![allow(unused_variables)]
fn main() {
use std::fmt::Display;

struct Pair<T> {
    x: T,
    y: T,
}

impl<T> Pair<T> {
    fn new(x: T, y: T) -> Self {
        Self { x, y }
    }
}

impl<T: Display + PartialOrd> Pair<T> {
    fn cmp_display(&self) {
        if self.x >= self.y {
            println!("The largest member is x = {}", self.x);
        } else {
            println!("The largest member is y = {}", self.y);
        }
    }
}
}

覆盖实现

对满足trait约束的所有类型实现trait被称作覆盖实现。
其实覆盖实现就是批量为很多类型实现trait方法,但表述为覆盖总觉得不太对劲。
代码如下,利用了for+泛型来实现:

impl<T: Display> ToString for T {
    // --snip--
}

最典型的就是为实现了Display trait的类型调用ToString trait中的to_String方法。

模块化有助于代码的管理和层次逻辑的清晰Rust模块化有多种方式: 1. 嵌套模块嵌套模块就是直接在要使用模块的文件中声明模块mod food{//声明模块 pub struct Cake; pub struct ...