trait 告诉编译器某个特定类型拥有可能与其他类型共享的功能。可以通过 trait 以一种抽象的方式定义共享的行为。可以使用 trait bounds 指定泛型是任何拥有特定行为的类型。

trait 类似于其他语言中的 接口,虽然有一些不同。

定义 trait

  1. pub trait Summary {
  2. // 一行一个方法签名且都以分号结尾
  3. fn summarize(&self) -> String;
  4. }

为类型实现 trait

  1. pub trait Summary {
  2. fn summarize(&self) -> String;
  3. }
  4. pub struct NewsArticle {
  5. pub headline: String,
  6. pub location: String,
  7. pub author: String,
  8. pub content: String,
  9. }
  10. impl Summary for NewsArticle {
  11. fn summarize(&self) -> String {
  12. format!("{}, by {} ({})", self.headline, self.author, self.location)
  13. }
  14. }
  15. pub struct Tweet {
  16. pub username: String,
  17. pub content: String,
  18. pub reply: bool,
  19. pub retweet: bool,
  20. }
  21. impl Summary for Tweet {
  22. fn summarize(&self) -> String {
  23. format!("{}: {}", self.username, self.content)
  24. }
  25. }

使用时,trait 必须和类型一起引入作用域以便使用额外的 trait 方法

  1. use aggregator::{Summary, Tweet};
  2. fn main() {
  3. let tweet = Tweet {
  4. username: String.from("horse_ebooks"),
  5. conntent: String.from(
  6. "of course, as you probably already know, people"
  7. ),
  8. reply: false,
  9. retweet: false,
  10. };
  11. println!("1 new tweet: {}", tweet.summarize());
  12. }

不能为外部类型实现外部 trait,例如,不能在 aggregator crate 中为 Vec 实现 Display trait。这是因为 Display 和 Vec 都定义于标准库中,它们并不位于 aggregator crate 本地作用域中。这个特性叫 相干性(coherence)、孤儿规则(orphan rule)。

默认实现

trait 中某些或全部方法提供默认行为。

  1. pub trait Summary {
  2. fn summarize(&self) -> String {
  3. String::from("(Read more...)")
  4. }
  5. }

trait 作为参数

为 NewsArticle 和 Tweet 类型实现了 Summary trait。我们可以定义一个函数 notify 来调用其参数 item 上的 summarize 方法,该参数是实现了 Summary trait 的某种类型。

  1. pub fn notify(item: &impl Summary) {
  2. println!("Breaking news! {}", item.summarize());
  3. }

我们可以传递任何 NewsArticle 或 Tweet 的实例来调用 notify。

Trait Bound 语法

impl Trait适用于短小的例子,trait bound 则适用于更复杂的场景。

  1. pub fn notify<T: Summary>(item1: &T, item2: &T) {

泛型 T 被指定为 item1 和 item2 的参数限制,如此传递给参数 item1 和 item2 值的具体类型必须一致。

通过 + 指定多个 trait bound

如果 notify 需要显示 item 的格式化形式,同时也要使用 summarize 方法,那么 item 就需要同时实现两个不同的 trait:Display 和 Summary。

  1. pub fn notify(item: &(impl Summary + Display)) {

+语法也适用于泛型的 trait bound:

  1. pub fn notify<T: Summary + Display>(item: &T) {

通过指定这两个 trait bound,notify 的函数体可以调用 summarize 并使用 {} 来格式化 item。

通过 where 简化 trait bound

  1. fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {

上述信息过长

  1. fn some_function<T, U>(t: &T, u: &U) -> i32
  2. where T: Display + Clone,
  3. U: Clone + Debug
  4. {

返回实现了 trait 的类型

  1. fn returns_summarizable() -> impl Summary {
  2. Tweet {
  3. username: String::from("horse_ebooks"),
  4. content: String::from(
  5. "of course, as you probably already know, people",
  6. ),
  7. reply: false,
  8. retweet: false,
  9. }
  10. }

使用 trait bounds 来修复 largest 函数

  1. $ cargo run
  2. Compiling chapter10 v0.1.0 (file:///projects/chapter10)
  3. error[E0369]: binary operation `>` cannot be applied to type `T`
  4. --> src/main.rs:5:17
  5. |
  6. 5 | if item > largest {
  7. | ---- ^ ------- T
  8. | |
  9. | T
  10. |
  11. help: consider restricting type parameter `T`
  12. |
  13. 1 | fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> T {
  14. | ++++++++++++++++++++++
  15. For more information about this error, try `rustc --explain E0369`.
  16. error: could not compile `chapter10` due to previous error

在 largest 函数体中我们想要使用大于运算符(>)比较两个 T 类型的值。这个运算符被定义为标准库中 trait std::cmp::PartialOrd 的一个默认方法。所以需要在 T 的 trait bound 中指定 PartialOrd,这样 largest 函数可以用于任何可以比较大小的类型的 slice。

  1. fn largest<T: PartialOrd>(list: &[T]) -> T {
  1. $ cargo run
  2. Compiling chapter10 v0.1.0 (file:///projects/chapter10)
  3. error[E0508]: cannot move out of type `[T]`, a non-copy slice
  4. --> src/main.rs:2:23
  5. |
  6. 2 | let mut largest = list[0];
  7. | ^^^^^^^
  8. | |
  9. | cannot move out of here
  10. | move occurs because `list[_]` has type `T`, which does not implement the `Copy` trait
  11. | help: consider borrowing here: `&list[0]`
  12. error[E0507]: cannot move out of a shared reference
  13. --> src/main.rs:4:18
  14. |
  15. 4 | for &item in list {
  16. | ----- ^^^^
  17. | ||
  18. | |data moved here
  19. | |move occurs because `item` has type `T`, which does not implement the `Copy` trait
  20. | help: consider removing the `&`: `item`
  21. Some errors have detailed explanations: E0507, E0508.
  22. For more information about an error, try `rustc --explain E0507`.
  23. 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!

  1. fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
  2. let mut largest = list[0];
  3. for &item in list {
  4. if item > largest {
  5. largest = item;
  6. }
  7. }
  8. largest
  9. }
  10. fn main() {
  11. let number_list = vec![34, 50, 25, 100, 65];
  12. let result = largest(&number_list);
  13. println!("The largest number is {}", result);
  14. let char_list = vec!['y', 'm', 'a', 'q'];
  15. let result = largest(&char_list);
  16. println!("The largest char is {}", result);
  17. }

使用 trait bound 有条件地实现方法

通过使用带有 trait bound 的泛型参数的 impl块,可以有条件地只为那些实现了特定 trait 的类型实现方法

  1. use std::fmt::Display;
  2. struct Pair<T> {
  3. x: T,
  4. y: T,
  5. }
  6. impl<T> Pair<T> {
  7. fn new(x: T, y: T) -> Self {
  8. Self { x, y }
  9. }
  10. }
  11. impl<T: Display + PartialOrd> Pair<T> {
  12. fn cmp_display(&self) {
  13. if self.x >= self.y {
  14. println!("The largest member is x = {}", self.x);
  15. } else {
  16. println!("The largest member is y = {}", self.y);
  17. }
  18. }
  19. }

类型 Pair<T>总是实现了 new方法并返回一个 Pair<T>的实例,不过在下一个 impl块中,只有那些为 T类型实现了PartialOrdtrait 和Displaytrait 的Pair<T>才会实现cmp_display。对任何满足特定 trait bound 的类型实现 trait 被称为 blanket implementations。