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

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

定义trait

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

例1:

1
2
3
4
5
6
7
pub trait Summary {
    fn summarize_author(&self) -> String;

    fn summarize(&self) -> String {
        format!("(Read more from {}...)", self.summarize_author())
    }
}
  • 例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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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)
    }
}


fn main() {
   let tweet = Tweet {
       username: String::from("haha"),
       content: String::from("the content"),
       reply: false,
       retweet: false,
   };
  println!("{}",  tweet.summarize())
}

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

trait作为函数的参数

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

例3:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
...

fn notify(summary: impl Summary) {
    println!("notify: {}",  summary.summarize())
}


fn main() {
   let tweet = Tweet {
       username: String::from("haha"),
       content: String::from("the content"),
       reply: false,
       retweet: false,
   };
  notify(tweet);
}

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

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

例4:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
...


fn notify_all(summaries: Vec<impl Summary>) {
    for summary in summaries {
        println!("notify: {}",  summary.summarize())
    }
}

fn notify(summary: impl Summary) {
    println!("notify: {}",  summary.summarize())
}

fn main() {
   let tweet = Tweet {
       username: String::from("haha"),
       content: String::from("the content"),
       reply: false,
       retweet: false,
   };
   let tweets = vec![tweet];
   notify_all(tweets);
}

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

例5:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
fn notify(summary: Box<dyn Summary>) {
    println!("notify: {}",  summary.summarize())
}

fn notify_all(summaries: Vec<Box<dyn Summary>>) {
    for summary in summaries {
        println!("notify: {}",  summary.summarize())
    }
}


fn main() {
   let tweet = Tweet {
       username: String::from("haha"),
       content: String::from("the content"),
       reply: false,
       retweet: false,
   };
   let tweets: Vec<Box<dyn Summary>> = vec![Box::new(tweet)];
   notify_all(tweets);
}

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

在泛型中使用trait

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

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

例6:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
fn notify<T: Summary>(summary: T) {
    println!("notify: {}",  summary.summarize())
}

fn notify_all<T: Summary>(summaries: Vec<T>) {
    for summary in summaries {
        println!("notify: {}",  summary.summarize())
    }
}


fn main() {
   let tweet = Tweet {
       username: String::from("haha"),
       content: String::from("the content"),
       reply: false,
       retweet: false,
   };
   let tweets = vec![tweet];
   notify_all(tweets);
}

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从句来简化,可改写成下面的形式:

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

参考