trait用来共享行为。与其它语言类比,就是相当于接口。
对于某一个行为,如果多种类型都需要实现它,那么就将其定义为一个trait
trait只定义不实现。
pub trait Summary {
fn summarize(&self) -> String;
}为类型实现trait
trait定义的行为终究还是需要实现的。
pub struct NewArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
impl Summary for NewArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location);
}
}pub struct Tweet {
pub username: String,
pub content: String,
pub reply: bool,
pub retweet: bool,
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content);
}
}Rust实现trait有一个规则,叫做孤儿规则:只有当trait或类型定义在我们自己的库里的时候,才能够为该类型实现对应的trait。这一规则的目的很简单,就是为了防止破坏别人的代码。
- 能够为别人的类型实现自己的trait。
- 能够为自己的类型实现别人的trait。
具有默认实现的trait
前面将的trait只有行为的定义,但是没有行为的实现,要求类型实现自己的特定类型的行为。 不过有时候,有的行为对于绝大部分类型都是一样的实现,通过trait的默认实现可以避免为这些类型的实现重复代码。
pub trait Summary {
fn summarize(&self) -> String {
String::from("(Read more...)")
}
}如果类型需要这种默认实现,就直接声明实现了trait即可,不需要再为默认的行为实现了。如
impl Summary for NewArticle {}如果需要特定的实现,再向前面所述的那样为类型实现trait即可。
trait作为参数接收具有统一行为的不同类型
这一点的用法是与其它语言的接口一致的。既要函数的参数能够接收不同的类型,但是又希望接收的类型是有限制的。
例如,定义一个notify函数来调用实现了Summary trait类型的summarize方法。
pub fn notify(item: impl Summary) {
println!("Breaking news! {}", item.summarize());
}但是trait约束其实是一种语法糖,其完整写法为:
pub fn notify<T: Summary>(item: T) {
println!("Breaking news! {}", item.summarize());
}使用加号可以指定多个trait约束。例如要求同时具有格式化函数:
pub fn notify(item: impl Summary + Display)泛型约束也可以使用加号
pub fn notify<T: Summary + Display>(item: T)当有多个泛型参数,并且每个泛型参数都有很多约束的时候,将这些约束还按照上面的写法就会让签名变得难以理解,因此可以使用where关键字将约束提取出来,保持函数签名的可读性。
fn some_function<T, U>(t: T, u: U) -> i32
where T:Display+Clone,
U:Clone+Debug
{}返回实现了trait的值
trait约束可以作为函数参数,当然也可以作为函数返回值。
fn returns_summarizable() -> impl Summary {
Tweet{
username: String::from("horse_ebooks"),
content: String::from("of course, as you probably already know, people"),
reply: false,
retweet: false,
}
}这一功能被闭包和迭代器高度依赖,可以创建出只有编译器才知道的签名长到难以想要的类型。
fn returns_summarizable(switch: bool) -> impl Summary {
if switch {
NewsArticle {
headline: String::from("Penguins win the Stanley Cup Championship!"),
location: String::from("Pittsburgh, PA, USA"),
author: String::from("Iceburgh"),
content: String::from("The Pittsburgh Penguins once again are the best hockey team in the NHL."),
}
} else {
Tweet {
username: String::from("horse_ebooks"),
content: String::from("of course, as you probably already know, people"),
reply: false,
retweet: false,
}
}
}使用trait约束来有条件地实现方法
通过带泛型参数的impl代码块中使用trait约束,可以为实现了指定trait的类型实现方法。
use std::fmt::Display;
struct Pair<T> {
x: T,
y: T,
}
impl<T> Pair<T> {
fn new(x: T, y: T) -> Self {
Self {
x,
y,
}
}
}
impl<T: Display + PartialOrd> Pair<T> {
fn cmp_display(&self) {
if self.x >= self.y {
println!("The largest member is x = {}", self.x);
} else {
println!("The largest member is y = {}", self.y);
}
}
}如上,类型Pair<T>都会实现new函数,但是只有实现了Display和PartialOrd的trait的内部类型T参会实现cmp_display函数。
例如标准库为所有实现了Display trait的类型实现了ToString trait。
impl <T: Display> ToString for T {
//略
}使用trait和trait约束,可以在使用泛型消除重复代码的同时,向编译器表明自己期望放心所具有的功能。