trait类似于其他编程语言中的常被称为接口(interface)的功能,但还是有一些区别的。 trait告诉Rust编译器某个特定类型拥有可能与其他类型共享的功能。可以通过trait以一种抽象的方式定义共享的行为。可以使用trait bounds指定泛型是任何拥有特定行为的类型。

简单的理解,trait就是Rust中的接口,定义了某个类型使用这个接口时的行为。使用trait可以约束多个类型之间共享的行为,在使用泛型编程时还能限制泛型必须符合trait规定的行为。

定义trait

trait是定义了一系列方法的接口。

例1:

1pub trait Summary {
2    fn summarize_author(&self) -> String;
3
4    fn summarize(&self) -> String {
5        format!("(Read more from {}...)", self.summarize_author())
6    }
7}
  • 例1定义了一个名称为Summary的trait,它包含summarize_authorsummarize两个方法提供的行为。
  • trait的方法是可以有默认实现的,这里的summarize方法就包含默认实现,并且这里在summarize方法默认实现内部还调用了不包含默认实现的summarize_author方法。
  • Summary trait的两个方法的参数中都包含关键字self,与结构体方法一样,self用作trait方法的第一个参数。
    • 实际上selfself: Self的简写,&selfself: &Self的简写,&mut selfself &mut Self的简写。
    • Self代表的是当前实现了trait的类型,例如有一个类型Foo实现了Summary trait,则实现方法时中的Self就是Foo。

为类型实现trait

例2:

 1pub trait Summary {
 2
 3    fn summarize_author(&self) -> String;
 4
 5    fn summarize(&self) -> String {
 6        format!("(Read more from {}...)", self.summarize_author())
 7    }
 8}
 9
10pub struct Tweet {
11    pub username: String,
12    pub content: String,
13    pub reply: bool,
14    pub retweet: bool,
15}
16
17
18impl Summary for Tweet {
19    fn summarize_author(&self) -> String {
20        format!("@{}", self.username)
21    }
22}
23
24
25fn main() {
26   let tweet = Tweet {
27       username: String::from("haha"),
28       content: String::from("the content"),
29       reply: false,
30       retweet: false,
31   };
32  println!("{}",  tweet.summarize())
33}

例2中为结构体类型Tweet实现了Summary trait,注意因为Summary trait中包含summarize方法的默认试下,所以这一版本里,只实现了summarize_author方法。

trait作为函数的参数

学习了如何定义trait和让某个类型实现trait,接下来看trait能否作为函数的参数,使函数能接受来自多个不同类型的参数。

例3:

 1...
 2
 3fn notify(summary: impl Summary) {
 4    println!("notify: {}",  summary.summarize())
 5}
 6
 7
 8fn main() {
 9   let tweet = Tweet {
10       username: String::from("haha"),
11       content: String::from("the content"),
12       reply: false,
13       retweet: false,
14   };
15  notify(tweet);
16}

例3中notify函数的summary参数的类型是impl Summary,而不是具体的类型。这样该参数就支持任何实现了Summary trait的类型。

如果我们在例3的基础上,编写一个notify_all的函数:

例4:

 1...
 2
 3
 4fn notify_all(summaries: Vec<impl Summary>) {
 5    for summary in summaries {
 6        println!("notify: {}",  summary.summarize())
 7    }
 8}
 9
10fn notify(summary: impl Summary) {
11    println!("notify: {}",  summary.summarize())
12}
13
14fn main() {
15   let tweet = Tweet {
16       username: String::from("haha"),
17       content: String::from("the content"),
18       reply: false,
19       retweet: false,
20   };
21   let tweets = vec![tweet];
22   notify_all(tweets);
23}

在这里复习对比一下前面学习智能指针Box时,Box的使用场景3: “当希望拥有一个值并只关心它的类型是否实现了特定trait而不是其具体类型的时候,可以使用Box”。

例5:

 1fn notify(summary: Box<dyn Summary>) {
 2    println!("notify: {}",  summary.summarize())
 3}
 4
 5fn notify_all(summaries: Vec<Box<dyn Summary>>) {
 6    for summary in summaries {
 7        println!("notify: {}",  summary.summarize())
 8    }
 9}
10
11
12fn main() {
13   let tweet = Tweet {
14       username: String::from("haha"),
15       content: String::from("the content"),
16       reply: false,
17       retweet: false,
18   };
19   let tweets: Vec<Box<dyn Summary>> = vec![Box::new(tweet)];
20   notify_all(tweets);
21}

上面的例4和例5有什么区别呢?

在泛型中使用trait

最后来看一下在泛型编程中使用trait限制泛型类型的行为。

在例3和例4的notify函数fn notify(summary: impl Summary),对于summary参数类型,指定的是impl关键字加trait名称,而不是具体的类型。 实际上impl Summary是泛型编程中Trait Bound的语法糖,以例4为例,我们可以改写为以下形式。

例6:

 1fn notify<T: Summary>(summary: T) {
 2    println!("notify: {}",  summary.summarize())
 3}
 4
 5fn notify_all<T: Summary>(summaries: Vec<T>) {
 6    for summary in summaries {
 7        println!("notify: {}",  summary.summarize())
 8    }
 9}
10
11
12fn main() {
13   let tweet = Tweet {
14       username: String::from("haha"),
15       content: String::from("the content"),
16       reply: false,
17       retweet: false,
18   };
19   let tweets = vec![tweet];
20   notify_all(tweets);
21}

impl Summary语法简洁,适用于短小的例子,Trait Bound适用于更复杂的场景,例如pub fn notify(item1: impl Summary, item2: impl Summary)就不如pub fn notify<T: Summary>(item1: T, item2: T)简洁了。

还可以通过+指定多个trait bound,例如: pub fn notify(item: impl Summary + Display)pub fn notify<T: Summary + Display>(item: T)

当出现多个泛型类型时,过多的trait bound会导致函数签名难以阅读,例如fn some_function<T: Display + Clone, U: Clone + Debug>(t: T, u: U) -> i32 {,此时可以使用where从句来简化,可改写成下面的形式:

1fn some_function<T, U>(t: T, u: U) -> i32
2    where T: Display + Clone,
3          U: Clone + Debug
4{
5    ...

参考