trait 告诉编译器某个特定类型拥有可能与其他类型共享的功能。可以通过 trait 以一种抽象的方式定义共享的行为。可以使用 trait bounds 指定泛型是任何拥有特定行为的类型。
trait 类似于其他语言中的 接口,虽然有一些不同。
定义 trait
pub trait Summary {
// 一行一个方法签名且都以分号结尾
fn summarize(&self) -> String;
}
为类型实现 trait
pub trait Summary {
fn summarize(&self) -> String;
}
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 必须和类型一起引入作用域以便使用额外的 trait 方法
use aggregator::{Summary, Tweet};
fn main() {
let tweet = Tweet {
username: String.from("horse_ebooks"),
conntent: String.from(
"of course, as you probably already know, people"
),
reply: false,
retweet: false,
};
println!("1 new tweet: {}", tweet.summarize());
}
不能为外部类型实现外部 trait,例如,不能在 aggregator crate 中为 Vec
默认实现
trait 中某些或全部方法提供默认行为。
pub trait Summary {
fn summarize(&self) -> String {
String::from("(Read more...)")
}
}
trait 作为参数
为 NewsArticle 和 Tweet 类型实现了 Summary trait。我们可以定义一个函数 notify 来调用其参数 item 上的 summarize 方法,该参数是实现了 Summary trait 的某种类型。
pub fn notify(item: &impl Summary) {
println!("Breaking news! {}", item.summarize());
}
我们可以传递任何 NewsArticle 或 Tweet 的实例来调用 notify。
Trait Bound 语法
impl Trait
适用于短小的例子,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
fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {
上述信息过长
fn some_function<T, U>(t: &T, u: &U) -> i32
where T: Display + Clone,
U: Clone + Debug
{
返回实现了 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,
}
}
使用 trait bounds 来修复 largest 函数
$ cargo run
Compiling chapter10 v0.1.0 (file:///projects/chapter10)
error[E0369]: binary operation `>` cannot be applied to type `T`
--> src/main.rs:5:17
|
5 | if item > largest {
| ---- ^ ------- T
| |
| T
|
help: consider restricting type parameter `T`
|
1 | fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> T {
| ++++++++++++++++++++++
For more information about this error, try `rustc --explain E0369`.
error: could not compile `chapter10` due to previous error
在 largest 函数体中我们想要使用大于运算符(>)比较两个 T 类型的值。这个运算符被定义为标准库中 trait std::cmp::PartialOrd 的一个默认方法。所以需要在 T 的 trait bound 中指定 PartialOrd,这样 largest 函数可以用于任何可以比较大小的类型的 slice。
fn largest<T: PartialOrd>(list: &[T]) -> T {
$ cargo run
Compiling chapter10 v0.1.0 (file:///projects/chapter10)
error[E0508]: cannot move out of type `[T]`, a non-copy slice
--> src/main.rs:2:23
|
2 | let mut largest = list[0];
| ^^^^^^^
| |
| cannot move out of here
| move occurs because `list[_]` has type `T`, which does not implement the `Copy` trait
| help: consider borrowing here: `&list[0]`
error[E0507]: cannot move out of a shared reference
--> src/main.rs:4:18
|
4 | for &item in list {
| ----- ^^^^
| ||
| |data moved here
| |move occurs because `item` has type `T`, which does not implement the `Copy` trait
| help: consider removing the `&`: `item`
Some errors have detailed explanations: E0507, E0508.
For more information about an error, try `rustc --explain E0507`.
error: could not compile `chapter10` due to 2 previous errors
错误的核心是 cannot move out of type [T], a non-copy slice,对于非泛型版本的 largest 函数,我们只尝试了寻找最大的 i32 和 char。当我们将 largest 函数改成使用泛型后,现在 list 参数的类型就有可能是没有实现 Copy trait 的。这意味着我们可能不能将 list[0] 的值移动到 largest 变量中,这导致了上面的错误。
为了只对实现了 Copy 的类型调用这些代码,可以在 T 的 trait bounds 中增加 Copy!
fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
let mut largest = list[0];
for &item in list {
if item > largest {
largest = item;
}
}
largest
}
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let result = largest(&number_list);
println!("The largest number is {}", result);
let char_list = vec!['y', 'm', 'a', 'q'];
let result = largest(&char_list);
println!("The largest char is {}", result);
}
使用 trait bound 有条件地实现方法
通过使用带有 trait bound 的泛型参数的 impl
块,可以有条件地只为那些实现了特定 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
方法并返回一个 Pair<T>
的实例,不过在下一个 impl
块中,只有那些为 T
类型实现了PartialOrd
trait 和Display
trait 的Pair<T>
才会实现cmp_display
。对任何满足特定 trait bound 的类型实现 trait 被称为 blanket implementations。