rust语言基础学习: 使用trait定义接口
2020-07-19
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_author
和summarize
两个方法提供的行为。 - trait的方法是可以有默认实现的,这里的summarize方法就包含默认实现,并且这里在summarize方法默认实现内部还调用了不包含默认实现的summarize_author方法。
- Summary trait的两个方法的参数中都包含关键字
self
,与结构体方法一样,self用作trait方法的第一个参数。- 实际上
self
是self: Self
的简写,&self
是self: &Self
的简写,&mut self
是self &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 ...