trait 相似于其他编程言语中的常被称为接口(interface)的功能,但还是有一些差异的。 trait 告诉 Rust 编译器某个特定类型具有或许与其他类型同享的功能。能够经过 trait 以一种笼统的办法界说同享的行为。能够运用 trait bounds 指定泛型是任何具有特定行为的类型。
简单的了解,trait 便是 Rust 中的接口,界说了某个类型运用这个接口时的行为。运用 trait 能够束缚多个类型之间同享的行为,在运用泛型编程时还能束缚泛型有必要契合 trait 规定的行为。
界说 trait
假如不同的类型具有相同的行为,那么就能够界说一个 trait,然后为这些类型完成该 trait。界说 trait 便是把一些办法组合在一起,目的是界说一个完成某些目标所必需的行为和调集。
trait 是界说了一系列办法的接口:
pub trait Summary {
//trait里面的办法只需求写声明即可
fn summarize_author(&self) -> String;
// 界说为默许完成的办法,其他类型就无需再完成该办法
fn summarize(&self) -> String {
format!("(Read more from {}...)", self.summarize_author())
}
}
- 界说了一个名称为
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。
pub trait Summary {
fn summarize_author(&self) -> String;
fn summarize(&self) -> String {
format!("@{}宣布了帖子...", self.summarize_author())
}
}
pub struct Tweet {
pub username: String,
pub content: String,
pub reply: bool,
pub retweet: bool,
}
pub struct Post {
pub title: String, // 标题
pub author: String, // 作者
pub content: String, // 内容
}
impl Summary for Post {
fn summarize_author(&self) -> String {
format!("{}宣布了贴子", self.author)
}
fn summarize(&self) -> String {
format!("{}宣布了贴子:{}", self.author, self.content)
}
}
impl Summary for Tweet {
fn summarize_author(&self) -> String {
format!("{}宣布了微博", self.username)
}
fn summarize(&self) -> String {
format!("@{}宣布了微博:{}", self.username, self.content)
}
}
fn main() {
let tweet = Tweet {
username: String::from("haha"),
content: String::from("the content"),
reply: false,
retweet: false,
};
println!("{}", tweet.summarize())
}
关于 trait 完成与界说的位置,有一条非常重要的原则(孤儿原则):假如你想要为类型A
完成traitT
,那么A
或许T
至少有一个是在当前作用域中界说的!
这个规则能够确保其他人编写的代码不会损坏你的代码,也确保了你不会莫名其妙损坏他人的代码。
运用 trait 作为函数参数
Trait 能够用作函数参数,这儿先界说一个函数,运用 trait 用作函数参数:
pub fn notify(item: &impl Summary) { // trait 参数
println!("Breaking news! {}", item.summarize());
}
参数的意思便是完成了 Summary trait 的 item 参数。能够运用任何完成了Summary
trait 的类型作为该函数的参数,一起在函数体内,还能够调用该 trait 的办法。
Trait 束缚 (trait bound)
上面运用的impl Trait
实际上仅仅一个语法糖,其完好书写方法如下,形如T: Summary
被称为 trait 束缚。
pub fn notify<T: Summary> (item: &T) { // trait 束缚
println!("Breaking news! {}", item.summarize());
}
关于比较杂乱的运用场景,特征束缚能够让咱们具有更大的灵活性和语法表现能力,例如一个函数承受两个impl Summary
的参数:
pub fn notify(item1: &impl Summary, item2: &impl Summary) {} // trait 参数
pub fn notify<T: Summary>(item1: &T, item2: &T) {} // 泛型 T 束缚:阐明 item1 和 item2 有必要具有同样的类型,一起阐明 T 有必要完成 Summary trait
经过+
指定多个 trait bound
除了单个束缚条件,还能够指定多个束缚条件,例如让参数完成多个 trait:
pub fn notify(item: &(impl Summary + Display)) {} // 语法糖方法
pub fn notify<T: Summary + Display> (item: &T) {} // trait bound 完好方法
经过where
简化 trait bound
当特征束缚变得很多时,函数的签名就会变得很杂乱,这时假如运用where
能够对其做一些方法上的改善:
// 当出现多个泛型类型时,过多的trait bound会导致函数签名难以阅览
fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 { ... }
// 运用 where 做简化,使得函数名、参数列表和回来值类型都离得很近,看起来跟没有那么多 trait bounds 的函数很像
fn some_function<T, U>(t: &T, u: &U) -> i32
where T: Display + Clone,
U: Clone + Debug {
....
}
运用 trait 束缚有条件地完成办法或 trait
trait 束缚作为参数,能够让咱们在指定类型+指定 trait 的条件下完成办法,使函数能承受来自多个不同类型的参数。
例如:
fn notify(summary: impl Summary) {
println!("notify: {}", summary.summarize())
}
fn notify_all(summaries: Vec<impl Summary>) {
for summary in summaries {
println!("notify: {}", summary.summarize())
}
}
fn main() {
let tweet = Weibo {
username: String::from("haha"),
content: String::from("the content"),
reply: false,
retweet: false,
};
let tweets = vec![tweet];
notify_all(tweets);
}
函数的summary参数的类型是impl Summary
,而不是详细的类型。这样该参数就支撑任何完成了Summary trait的类型。
当希望具有一个值并只关怀它的类型是否完成了特定 trait 而不是其详细类型的时候,能够运用智能指针 Box
和关键字 dyn
组合的 trait 目标(注:后续将独自章节学习 trait 目标)。
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);
}
在泛型中运用trait
最终来看一下在泛型编程中运用 trait 束缚泛型类型的行为。
上面界说 trait 比方中 notify 函数fn notify(summary: impl Summary)
,关于 summary 参数类型,指定的是 impl 关键字加 trait 名称,而不是详细的类型。 实际上impl Summary
是泛型编程中 Trait Bound 的语法糖,所以上面的impl trait 代码能够改写为以下方法:
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 Trait
能够经过impl Trait
来阐明一个函数回来了一个类型,该类型完成了某个 trait:
fn returns_summarizable() -> impl Summary {
Tweet {
username: String::from("haha"),
content: String::from("the content"),
reply: false,
retweet: false,
}
}
这种impl Trait
方法的回来值只能是单一类型的 trait,假如多个类型的 trait 就会报错,比方:
fn returns_summarizable(switch: bool) -> impl Summary {
if switch {
Tweet { ... } // 此处不能回来两个不同的类型
} else {
Post { ... } // 此处不能回来两个不同的类型
}
}
上面的代码会报错,因为回来了两个不同的类型 trait:Tweet
和Post
。假如想要完成回来不同的类型,需求运用trait 目标,关于这部分后续将独自章节学习。
fn returns_summarizable(switch: bool) -> Box<dyn Summary> {
if switch {
Box::new(Tweet { ... }) // trait 目标
} else {
Box::new(Post { ... }) // trait 目标
}
}
总结
在 Rust 设计目标中,零成本笼统是非常重要的一条,它让 Rust 具备高级言语表达能力的一起,又不会带来功能损耗。零成本的基石是泛型与 trait,它们能够在编译期把高级语法编译成与高效的底层代码,然后完成运行时的高效。
trait 是以笼统的办法界说同享的行为,trait bound 是对函数参数或许回来值界说类型束缚,比方impl SuperTrait
或 T: SuperTrait
,trait 和 trait bound 让咱们运用泛型类型参数来削减重复,并仍然能够向编译器清晰指定泛型类型需求具有哪些行为。因为咱们向编译器供给了 trait bound 信息,它就能够检查代码中所用到的详细类型是否供给了正确的行为。综上,trait 用途简单的归纳为:
- 笼统行为:相似接口,是对类型共性的同一性笼统,界说共同行为
- 类型束缚:类型的行为被 trait 限定在更有限的范围内
参阅
- kaisery.github.io/trpl-zh-cn/…
- course.rs/basic/trait…
- rustwiki.org/zh-CN/refer…
- rustwiki.org/zh-CN/rust-…