Trait¶
trait 定义了某个特定类型拥有可能与其他类型共享的功能。可以通过 trait 以一种抽象的方式定义共享的行为。可以使用 trait bounds 指定泛型是任何拥有特定行为的类型。
trait 类似于其他语言中的常被称为 接口(interfaces)的功能,虽然有一些不同。
定义trait¶
pub trait trait_name {
fn func_name(&self) -> String;
}
pub trait Summary {
fn summarize(&self) -> String;
}
为类型实现trait¶
pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
impl Summary for NewsArticle {
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)
}
}
但是不能为外部类型实现外部 trait。例如,不能在 aggregator
crate 中为 Vec<T>
实现 Display
trait。这是因为 Display
和 Vec<T>
都定义于标准库中,它们并不位于 aggregator
crate 本地作用域中。这个限制是被称为 相干性(coherence)的程序属性的一部分,或者更具体的说是 孤儿规则(orphan rule),其得名于不存在父类型。这条规则确保了其他人编写的代码不会破坏你代码,反之亦然。没有这条规则的话,两个 crate 可以分别对相同类型实现相同的 trait,而 Rust 将无从得知应该使用哪一个实现。
默认实现¶
pub trait Summary {
fn summarize(&self) -> String {
String::from("(Read more...)")
}
}
如果想要对 NewsArticle
实例使用这个默认实现,可以通过 impl Summary for NewsArticle {}
指定一个空的 impl
块。
struct simple {
x:i32,
}
pub trait content {
fn get_string(&self) {
println!("Hello")
}
}
impl content for simple {
}
fn main() {
let s = simple{x:3};
s.get_string()
}
默认实现允许调用相同 trait 中的其他方法,哪怕这些方法没有默认实现。
pub trait Summary {
fn summarize_author(&self) -> String;
fn summarize(&self) -> String {
format!("(Read more from {}...)", self.summarize_author())
}
}
为了使用这个版本的 Summary
,只需在实现 trait 时定义 summarize_author
即可:
impl Summary for Tweet {
fn summarize_author(&self) -> String {
format!("@{}", self.username)
}
}
trait 作为参数¶
我们可以探索一下如何使用 trait 来接受多种不同类型的参数。示例 10-13 中为 NewsArticle
和 Tweet
类型实现了 Summary
trait,用其来定义了一个函数 notify
来调用其参数 item
上的 summarize
方法,该参数是实现了 Summary
trait 的某种类型。为此可以使用 impl Trait
语法,像这样:
pub fn notify(item: &impl Summary) {
println!("Breaking news! {}", item.summarize());
}
对于 item
参数,我们指定了 impl
关键字和 trait 名称,而不是具体的类型。该参数支持任何实现了指定 trait 的类型。在 notify
函数体中,可以调用任何来自 Summary
trait 的方法,比如 summarize
。我们可以传递任何 NewsArticle
或 Tweet
的实例来调用 notify
。任何用其它如 String
或 i32
的类型调用该函数的代码都不能编译,因为它们没有实现 Summary
。
示例
struct Simple {
x:i32,
}
pub trait Content {
fn get_string(&self);
}
impl Content for Simple {
fn get_string(&self){
println!("Hello")
}
}
pub fn notify(item: &impl Content) {
item.get_string();
}
fn main() {
let s = Simple{x:3};
notify(&s); // 只能使用已经实现trait的类型
}
Trait Bound 语法¶
impl Trait
语法适用于直观的例子,它实际上是一种较长形式语法的语法糖。我们称为 trait bound,它看起来像:
pub fn notify<T: Summary>(item: &T) {
println!("Breaking news! {}", item.summarize());
}
使用 impl Trait
的语法看起来像这样:
pub fn notify(item1: &impl Summary, item2: &impl Summary) {
这适用于 item1
和 item2
允许是不同类型的情况(只要它们都实现了 Summary
)。不过如果你希望强制它们都是相同类型呢?这只有在使用 trait bound 时才有可能:
pub fn notify<T: Summary>(item1: &T, item2: &T) {
泛型 T
被指定为 item1
和 item2
的参数限制,如此传递给参数 item1
和 item2
值的具体类型必须一致。
通过 + 指定多个 trait bound¶
如果 notify 需要显示 item 的格式化形式,同时也要使用 summarize 方法,那么 item 就需要同时实现两个不同的 trait:Display 和 Summary。这可以通过 + 语法实现:
pub fn notify(item: &(impl Summary + Display)) {
- 语法也适用于泛型的 trait bound:
pub fn notify<T: Summary + Display>(item: &T) {
通过指定这两个 trait bound,notify 的函数体可以调用 summarize 并使用 {} 来格式化 item。
通过 where 简化 trait bound¶
然而,使用过多的 trait bound 也有缺点。每个泛型有其自己的 trait bound,所以有多个泛型参数的函数在名称和参数列表之间会有很长的 trait bound 信息,这使得函数签名难以阅读。为此,Rust 有另一个在函数签名之后的 where
从句中指定 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,
{
这个函数签名就显得不那么杂乱,函数名、参数列表和返回值类型都离得很近,看起来跟没有那么多 trait bounds 的函数很像。
返回实现了 trait 的类型¶
struct Simple {
x:i32,
}
pub trait Content {
fn get_string(&self);
}
impl Content for Simple {
fn get_string(&self){
println!("Hello")
}
}
pub fn notify(item: &impl Content) {
item.get_string();
}
fn return_fn() -> impl Content {
if flag {
Simple { //只能返回一种类型,不能返回不同的类型
x:42
}
}
// else {
// xxx {
// y:
// z:
//}
// }
}
fn main() {
let s = Simple{x:3};
notify(&s);
let simple = return_fn();
simple.get_string();
}
使用 trait bound 有条件的实现方法¶
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);
}
}
}
也可以对任何实现了特定 trait 的类型有条件地实现 trait。对任何满足特定 trait bound 的类型实现 trait 被称为 blanket implementations,他们被广泛的用于 Rust 标准库中。例如,标准库为任何实现了 Display
trait 的类型实现了 ToString
trait。这个 impl
块看起来像这样:
impl<T: Display> ToString for T {
// --snip--
}
因为标准库有了这些 blanket implementation,我们可以对任何实现了 Display
trait 的类型调用由 ToString
定义的 to_string
方法。例如,可以将整型转换为对应的 String
值,因为整型实现了 Display
:
let s = 3.to_string();