trait类似于其他编程语言中的常被称为接口(interface)的功能,但还是有一些区别的。 trait告诉Rust编译器某个特定类型拥有可能与其他类型共享的功能。可以通过trait以一种抽象的方式定义共享的行为。可以使用trait bounds指定泛型是任何拥有特定行为的类型。
简单的理解,trait就是Rust中的接口,定义了某个类型使用这个接口时的行为。使用trait可以约束多个类型之间共享的行为,在使用泛型编程时还能限制泛型必须符合trait规定的行为。
定义trait
trait是定义了一系列方法的接口。
例1:
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:
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:
...
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:
...
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:
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:
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从句来简化,可改写成下面的形式:
fn some_function<T, U>(t: T, u: U) -> i32
where T: Display + Clone,
U: Clone + Debug
{
...