【注意】最后更新于 July 19, 2020,文中内容可能已过时,请谨慎使用。
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_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:
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
|
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_author方法。
trait作为函数的参数
学习了如何定义trait和让某个类型实现trait,接下来看trait能否作为函数的参数,使函数能接受来自多个不同类型的参数。
例3:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
...
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
|
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
{
...
|
参考