Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

使用 Trait 定义共享行为

Defining Shared Behavior with Traits

一个 Trait 定义了某个特定类型拥有并可以与其他类型共享的功能。我们可以使用 Trait 以抽象的方式定义共享行为。我们可以使用 Trait bound 来指定泛型类型可以是任何具有特定行为的类型。

A trait defines the functionality a particular type has and can share with other types. We can use traits to define shared behavior in an abstract way. We can use trait bounds to specify that a generic type can be any type that has certain behavior.

注意:Trait 类似于其他语言中通常被称为 接口 (interfaces) 的功能,尽管存在一些差异。

Note: Traits are similar to a feature often called interfaces in other languages, although with some differences.

定义 Trait

Defining a Trait

一个类型的行为由我们可以在该类型上调用的方法组成。如果我们可以在所有这些类型上调用相同的方法,那么不同的类型就共享相同的行为。Trait 定义是一种将方法签名组合在一起,以定义完成某些目的所必需的一组行为的方法。

A type’s behavior consists of the methods we can call on that type. Different types share the same behavior if we can call the same methods on all of those types. Trait definitions are a way to group method signatures together to define a set of behaviors necessary to accomplish some purpose.

例如,假设我们有多个持有各种类型和数量文本的 struct:NewsArticle struct 持有在特定地点归档的新闻报道,而 SocialPost 则最多可以有 280 个字符,并附带指示它是新帖、转发还是对另一帖回复的元数据。

For example, let’s say we have multiple structs that hold various kinds and amounts of text: a NewsArticle struct that holds a news story filed in a particular location and a SocialPost that can have, at most, 280 characters along with metadata that indicates whether it was a new post, a repost, or a reply to another post.

我们想要创建一个名为 aggregator 的媒体聚合库 crate,它可以显示可能存储在 NewsArticleSocialPost 实例中的数据摘要。为此,我们需要来自每个类型的摘要,并且我们将通过调用实例上的 summarize 方法来请求该摘要。示例 10-12 展示了一个公有的 Summary Trait 的定义,它表达了这种行为。

We want to make a media aggregator library crate named aggregator that can display summaries of data that might be stored in a NewsArticle or SocialPost instance. To do this, we need a summary from each type, and we’ll request that summary by calling a summarize method on an instance. Listing 10-12 shows the definition of a public Summary trait that expresses this behavior.

pub trait Summary {
    fn summarize(&self) -> String;
}

在这里,我们使用 trait 关键字声明一个 Trait,然后是 Trait 的名称,在本例中是 Summary。我们还将 Trait 声明为 pub,以便依赖此 crate 的其他 crate 也可以使用此 Trait,正如我们将在接下来的几个示例中看到的那样。在花括号内,我们声明描述实现此 Trait 的类型的行为的方法签名,在本例中是 fn summarize(&self) -> String

Here, we declare a trait using the trait keyword and then the trait’s name, which is Summary in this case. We also declare the trait as pub so that crates depending on this crate can make use of this trait too, as we’ll see in a few examples. Inside the curly brackets, we declare the method signatures that describe the behaviors of the types that implement this trait, which in this case is fn summarize(&self) -> String.

在方法签名之后,我们使用分号而不是在花括号内提供实现。实现此 Trait 的每个类型都必须为方法体提供自己的自定义行为。编译器将强制要求任何具有 Summary Trait 的类型都必须定义具有完全相同签名的 summarize 方法。

After the method signature, instead of providing an implementation within curly brackets, we use a semicolon. Each type implementing this trait must provide its own custom behavior for the body of the method. The compiler will enforce that any type that has the Summary trait will have the method summarize defined with this signature exactly.

Trait 的主体中可以有多个方法:方法签名每行罗列一个,且每行以分号结尾。

A trait can have multiple methods in its body: The method signatures are listed one per line, and each line ends in a semicolon.

在类型上实现 Trait

Implementing a Trait on a Type

既然我们已经定义了 Summary Trait 方法所需的签名,我们就可以在媒体聚合器中的类型上实现它。示例 10-13 展示了在 NewsArticle struct 上实现 Summary Trait 的过程,该实现使用标题、作者和地点来创建 summarize 的返回值。对于 SocialPost struct,我们将 summarize 定义为用户名后跟帖子的完整文本,假设帖子内容已经被限制在 280 个字符以内。

Now that we’ve defined the desired signatures of the Summary trait’s methods, we can implement it on the types in our media aggregator. Listing 10-13 shows an implementation of the Summary trait on the NewsArticle struct that uses the headline, the author, and the location to create the return value of summarize. For the SocialPost struct, we define summarize as the username followed by the entire text of the post, assuming that the post content is already limited to 280 characters.

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 SocialPost {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub repost: bool,
}

impl Summary for SocialPost {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

在类型上实现 Trait 与实现普通方法类似。不同之处在于在 impl 之后,我们放入想要实现的 Trait 名称,然后使用 for 关键字,接着指定我们想要为其实现 Trait 的类型名称。在 impl 块内部,我们放入 Trait 定义中定义好的方法签名。我们不再在每个签名后添加分号,而是使用花括号,并在其中填充我们希望该 Trait 方法针对特定类型所具有的具体行为。

Implementing a trait on a type is similar to implementing regular methods. The difference is that after impl, we put the trait name we want to implement, then use the for keyword, and then specify the name of the type we want to implement the trait for. Within the impl block, we put the method signatures that the trait definition has defined. Instead of adding a semicolon after each signature, we use curly brackets and fill in the method body with the specific behavior that we want the methods of the trait to have for the particular type.

现在库已经在 NewsArticleSocialPost 上实现了 Summary Trait,crate 的用户就可以像调用普通方法一样,在 NewsArticleSocialPost 实例上调用 Trait 方法。唯一的区别是用户必须将 Trait 和类型都引入作用域。以下是一个 binary crate 如何使用我们的 aggregator 库 crate 的示例:

Now that the library has implemented the Summary trait on NewsArticle and SocialPost, users of the crate can call the trait methods on instances of NewsArticle and SocialPost in the same way we call regular methods. The only difference is that the user must bring the trait into scope as well as the types. Here’s an example of how a binary crate could use our aggregator library crate:

use aggregator::{SocialPost, Summary};

fn main() {
    let post = SocialPost {
        username: String::from("horse_ebooks"),
        content: String::from(
            "of course, as you probably already know, people",
        ),
        reply: false,
        repost: false,
    };

    println!("1 new post: {}", post.summarize());
}

这段代码会打印 1 new post: horse_ebooks: of course, as you probably already know, people

This code prints 1 new post: horse_ebooks: of course, as you probably already know, people.

依赖 aggregator crate 的其他 crate 也可以将 Summary Trait 引入作用域,以便在它们自己的类型上实现 Summary。需要注意的一个限制是,只有当 Trait 或类型(或两者)对于我们的 crate 是本地的时,我们才能在类型上实现该 Trait。例如,我们可以作为 aggregator crate 功能的一部分,在 SocialPost 这样的自定义类型上实现像 Display 这样的标准库 Trait,因为 SocialPost 类型对于我们的 aggregator crate 是本地的。我们也可以在 aggregator crate 中为 Vec<T> 实现 Summary,因为 Summary Trait 对于我们的 aggregator crate 是本地的。

Other crates that depend on the aggregator crate can also bring the Summary trait into scope to implement Summary on their own types. One restriction to note is that we can implement a trait on a type only if either the trait or the type, or both, are local to our crate. For example, we can implement standard library traits like Display on a custom type like SocialPost as part of our aggregator crate functionality because the type SocialPost is local to our aggregator crate. We can also implement Summary on Vec<T> in our aggregator crate because the trait Summary is local to our aggregator crate.

但我们不能在外部类型上实现外部 Trait。例如,我们不能在 aggregator crate 中为 Vec<T> 实现 Display Trait,因为 DisplayVec<T> 都在标准库中定义,对于我们的 aggregator crate 来说都不是本地的。这个限制是被称为 相干性 (coherence) 属性的一部分,更具体地说叫做 孤儿规则 (orphan rule),因其父类型不存在而得名。这条规则确保了别人的代码不会破坏你的代码,反之亦然。如果没有这条规则,两个 crate 可能会为同一个类型实现同一个 Trait,而 Rust 将不知道该使用哪个实现。

But we can’t implement external traits on external types. For example, we can’t implement the Display trait on Vec<T> within our aggregator crate, because Display and Vec<T> are both defined in the standard library and aren’t local to our aggregator crate. This restriction is part of a property called coherence, and more specifically the orphan rule, so named because the parent type is not present. This rule ensures that other people’s code can’t break your code and vice versa. Without the rule, two crates could implement the same trait for the same type, and Rust wouldn’t know which implementation to use.

使用默认实现

Using Default Implementations

有时为 Trait 中的某些或全部方法提供默认行为是很有用的,而不是要求在每个类型上都实现所有方法。这样,当我们针对特定类型实现 Trait 时,我们可以保留或重写每个方法的默认行为。

Sometimes it’s useful to have default behavior for some or all of the methods in a trait instead of requiring implementations for all methods on every type. Then, as we implement the trait on a particular type, we can keep or override each method’s default behavior.

在示例 10-14 中,我们为 Summary Trait 的 summarize 方法指定了一个默认字符串,而不是像示例 10-12 那样仅定义方法签名。

In Listing 10-14, we specify a default string for the summarize method of the Summary trait instead of only defining the method signature, as we did in Listing 10-12.

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

pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

impl Summary for NewsArticle {}

pub struct SocialPost {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub repost: bool,
}

impl Summary for SocialPost {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

为了使用默认实现来聚合 NewsArticle 的实例,我们指定一个空的 impl 块:impl Summary for NewsArticle {}

To use a default implementation to summarize instances of NewsArticle, we specify an empty impl block with impl Summary for NewsArticle {}.

尽管我们不再直接在 NewsArticle 上定义 summarize 方法,但我们提供了一个默认实现,并指定 NewsArticle 实现了 Summary Trait。因此,我们仍然可以调用 NewsArticle 实例上的 summarize 方法,如下所示:

Even though we’re no longer defining the summarize method on NewsArticle directly, we’ve provided a default implementation and specified that NewsArticle implements the Summary trait. As a result, we can still call the summarize method on an instance of NewsArticle, like this:

use aggregator::{self, NewsArticle, Summary};

fn main() {
    let article = 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.",
        ),
    };

    println!("New article available! {}", article.summarize());
}

这段代码会打印 New article available! (Read more...)

This code prints New article available! (Read more...).

创建一个默认实现并不要求我们更改示例 10-13 中 SocialPostSummary 实现。原因是重写默认实现的语法与实现没有默认实现的 Trait 方法的语法相同。

Creating a default implementation doesn’t require us to change anything about the implementation of Summary on SocialPost in Listing 10-13. The reason is that the syntax for overriding a default implementation is the same as the syntax for implementing a trait method that doesn’t have a default implementation.

默认实现可以调用同一 Trait 中的其他方法,即使这些其他方法没有默认实现。通过这种方式,Trait 可以提供很多有用的功能,并仅要求实现者指定其中的一小部分。例如,我们可以定义 Summary Trait 拥有一个必须实现的 summarize_author 方法,然后定义一个具有调用 summarize_author 方法的默认实现的 summarize 方法:

Default implementations can call other methods in the same trait, even if those other methods don’t have a default implementation. In this way, a trait can provide a lot of useful functionality and only require implementors to specify a small part of it. For example, we could define the Summary trait to have a summarize_author method whose implementation is required, and then define a summarize method that has a default implementation that calls the summarize_author method:

pub trait Summary {
    fn summarize_author(&self) -> String;

    fn summarize(&self) -> String {
        format!("(Read more from {}...)", self.summarize_author())
    }
}

pub struct SocialPost {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub repost: bool,
}

impl Summary for SocialPost {
    fn summarize_author(&self) -> String {
        format!("@{}", self.username)
    }
}

要使用这个版本的 Summary,当我们为某个类型实现 Trait 时,只需要定义 summarize_author

To use this version of Summary, we only need to define summarize_author when we implement the trait on a type:

pub trait Summary {
    fn summarize_author(&self) -> String;

    fn summarize(&self) -> String {
        format!("(Read more from {}...)", self.summarize_author())
    }
}

pub struct SocialPost {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub repost: bool,
}

impl Summary for SocialPost {
    fn summarize_author(&self) -> String {
        format!("@{}", self.username)
    }
}

定义了 summarize_author 后,我们就可以在 SocialPost struct 的实例上调用 summarizesummarize 的默认实现将调用我们提供的 summarize_author 定义。因为我们实现了 summarize_authorSummary Trait 就赋予了我们 summarize 方法的行为,而不需要我们再编写任何代码。以下是它的样子:

After we define summarize_author, we can call summarize on instances of the SocialPost struct, and the default implementation of summarize will call the definition of summarize_author that we’ve provided. Because we’ve implemented summarize_author, the Summary trait has given us the behavior of the summarize method without requiring us to write any more code. Here’s what that looks like:

use aggregator::{self, SocialPost, Summary};

fn main() {
    let post = SocialPost {
        username: String::from("horse_ebooks"),
        content: String::from(
            "of course, as you probably already know, people",
        ),
        reply: false,
        repost: false,
    };

    println!("1 new post: {}", post.summarize());
}

这段代码会打印 1 new post: (Read more from @horse_ebooks...)

This code prints 1 new post: (Read more from @horse_ebooks...).

请注意,无法从同一方法的重写实现中调用默认实现。

Note that it isn’t possible to call the default implementation from an overriding implementation of that same method.

使用 Trait 作为参数

Using Traits as Parameters

既然你已经知道如何定义和实现 Trait,我们就可以探索如何使用 Trait 来定义接受多种不同类型的函数。我们将使用示例 10-13 中在 NewsArticleSocialPost 类型上实现的 Summary Trait 来定义一个 notify 函数,该函数在其 item 参数上调用 summarize 方法,该参数属于实现了 Summary Trait 的某种类型。为此,我们使用 impl Trait 语法,如下所示:

Now that you know how to define and implement traits, we can explore how to use traits to define functions that accept many different types. We’ll use the Summary trait we implemented on the NewsArticle and SocialPost types in Listing 10-13 to define a notify function that calls the summarize method on its item parameter, which is of some type that implements the Summary trait. To do this, we use the impl Trait syntax, like this:

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 SocialPost {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub repost: bool,
}

impl Summary for SocialPost {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

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

我们不为 item 参数指定具体类型,而是指定 impl 关键字和 Trait 名称。此参数接受实现指定 Trait 的任何类型。在 notify 的函数体中,我们可以调用 item 上来自 Summary Trait 的任何方法,例如 summarize。我们可以调用 notify 并传入 NewsArticleSocialPost 的任何实例。使用任何其他类型(例如 Stringi32)调用该函数的代码将无法编译,因为这些类型没有实现 Summary

Instead of a concrete type for the item parameter, we specify the impl keyword and the trait name. This parameter accepts any type that implements the specified trait. In the body of notify, we can call any methods on item that come from the Summary trait, such as summarize. We can call notify and pass in any instance of NewsArticle or SocialPost. Code that calls the function with any other type, such as a String or an i32, won’t compile, because those types don’t implement Summary.

Trait Bound 语法

Trait Bound Syntax

impl Trait 语法在简单情况下适用,但它实际上是一种被称为 Trait bound 的更长形式的语法糖;它看起来像这样:

pub fn notify<T: Summary>(item: &T) {
    println!("Breaking news! {}", item.summarize());
}

这种更长的形式与上一节中的示例等效,但更加冗长。我们将 Trait bound 与泛型类型参数的声明放在一起,位于冒号之后且在尖括号内。

This longer form is equivalent to the example in the previous section but is more verbose. We place trait bounds with the declaration of the generic type parameter after a colon and inside angle brackets.

impl Trait 语法很方便,并且在简单情况下能使代码更简洁,而更完整的 Trait bound 语法则可以在其他情况下表达更多复杂性。例如,我们可以有两个实现了 Summary 的参数。使用 impl Trait 语法的做法如下:

The impl Trait syntax is convenient and makes for more concise code in simple cases, while the fuller trait bound syntax can express more complexity in other cases. For example, we can have two parameters that implement Summary. Doing so with the impl Trait syntax looks like this:

pub fn notify(item1: &impl Summary, item2: &impl Summary) {

如果我们希望此函数允许 item1item2 具有不同的类型(只要两种类型都实现了 Summary),使用 impl Trait 是合适的。然而,如果我们想强制两个参数具有相同的类型,我们就必须使用 Trait bound,如下所示:

Using impl Trait is appropriate if we want this function to allow item1 and item2 to have different types (as long as both types implement Summary). If we want to force both parameters to have the same type, however, we must use a trait bound, like this:

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

指定为 item1item2 参数类型的泛型类型 T 约束了该函数,使得作为 item1item2 的实参传入的值的具体类型必须相同。

The generic type T specified as the type of the item1 and item2 parameters constrains the function such that the concrete type of the value passed as an argument for item1 and item2 must be the same.

通过 + 语法指定多个 Trait Bound

Multiple Trait Bounds with the + Syntax

我们还可以指定多个 Trait bound。假设我们希望 notifyitem 上既能使用显示格式又能使用 summarize:我们在 notify 定义中指定 item 必须同时实现 DisplaySummary。我们可以使用 + 语法来实现:

We can also specify more than one trait bound. Say we wanted notify to use display formatting as well as summarize on item: We specify in the notify definition that item must implement both Display and Summary. We can do so using the + syntax:

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

+ 语法在泛型类型的 Trait bound 上同样有效:

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

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

With the two trait bounds specified, the body of notify can call summarize and use {} to format item.

通过 where 子句简化 Trait Bound

Clearer Trait Bounds with where Clauses

使用太多的 Trait bound 也有其缺点。每个泛型都有自己的 Trait bound,因此具有多个泛型类型参数的函数在函数名和参数列表之间可能会包含大量的 Trait bound 信息,从而使函数签名难以阅读。出于这个原因,Rust 提供了另一种语法,用于在函数签名之后的 where 子句中指定 Trait bound。所以,不要这样写:

Using too many trait bounds has its downsides. Each generic has its own trait bounds, so functions with multiple generic type parameters can contain lots of trait bound information between the function’s name and its parameter list, making the function signature hard to read. For this reason, Rust has alternate syntax for specifying trait bounds inside a where clause after the function signature. So, instead of writing this:

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

我们可以使用 where 子句,如下所示:

we can use a where clause, like this:

fn some_function<T, U>(t: &T, u: &U) -> i32
where
    T: Display + Clone,
    U: Clone + Debug,
{
    unimplemented!()
}

这个函数的签名看起来不那么拥挤:函数名、参数列表和返回类型都靠在一起,类似于没有大量 Trait bound 的函数。

This function’s signature is less cluttered: The function name, parameter list, and return type are close together, similar to a function without lots of trait bounds.

返回实现了 Trait 的类型

Returning Types That Implement Traits

我们还可以在返回位置使用 impl Trait 语法,以返回实现了 Trait 的某种类型的值,如下所示:

We can also use the impl Trait syntax in the return position to return a value of some type that implements a trait, as shown here:

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 SocialPost {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub repost: bool,
}

impl Summary for SocialPost {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

fn returns_summarizable() -> impl Summary {
    SocialPost {
        username: String::from("horse_ebooks"),
        content: String::from(
            "of course, as you probably already know, people",
        ),
        reply: false,
        repost: false,
    }
}

通过在返回类型中使用 impl Summary,我们指定 returns_summarizable 函数返回某种实现了 Summary Trait 的类型,而无需指出具体的类型。在这种情况下,returns_summarizable 返回一个 SocialPost,但调用此函数的代码不需要知道这一点。

By using impl Summary for the return type, we specify that the returns_summarizable function returns some type that implements the Summary trait without naming the concrete type. In this case, returns_summarizable returns a SocialPost, but the code calling this function doesn’t need to know that.

仅通过它实现的 Trait 来指定返回类型的能力,在闭包和迭代器的上下文中特别有用,我们将在第 13 章中讨论这些内容。闭包和迭代器创建了只有编译器知道的类型,或者指定起来非常长的类型。impl Trait 语法让你能够简洁地指定函数返回某种实现了 Iterator Trait 的类型,而不需要写出非常长的类型。

The ability to specify a return type only by the trait it implements is especially useful in the context of closures and iterators, which we cover in Chapter 13. Closures and iterators create types that only the compiler knows or types that are very long to specify. The impl Trait syntax lets you concisely specify that a function returns some type that implements the Iterator trait without needing to write out a very long type.

但是,只有当你返回单一类型时,才能使用 impl Trait。例如,这段返回 NewsArticleSocialPost 且返回类型指定为 impl Summary 的代码将无法工作:

However, you can only use impl Trait if you’re returning a single type. For example, this code that returns either a NewsArticle or a SocialPost with the return type specified as impl Summary wouldn’t work:

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 SocialPost {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub repost: bool,
}

impl Summary for SocialPost {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

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 {
        SocialPost {
            username: String::from("horse_ebooks"),
            content: String::from(
                "of course, as you probably already know, people",
            ),
            reply: false,
            repost: false,
        }
    }
}

由于编译器中 impl Trait 语法的实现限制,不允许返回 NewsArticleSocialPost。我们将在第 18 章的 “使用 Trait 对象实现共享行为的抽象” 一节中讨论如何编写具有此类行为的函数。

Returning either a NewsArticle or a SocialPost isn’t allowed due to restrictions around how the impl Trait syntax is implemented in the compiler. We’ll cover how to write a function with this behavior in the “Using Trait Objects to Abstract over Shared Behavior” section of Chapter 18.

使用 Trait Bound 有条件地实现方法

Using Trait Bounds to Conditionally Implement Methods

通过将 Trait bound 与使用泛型类型参数的 impl 块结合使用,我们可以为实现了指定 Trait 的类型有条件地实现方法。例如,示例 10-15 中的 Pair<T> 类型始终实现 new 函数以返回 Pair<T> 的新实例(回想第 5 章 “方法语法” 一节,Selfimpl 块类型的类型别名,在本例中即为 Pair<T>)。但在下一个 impl 块中,只有当 Pair<T> 的内部类型 T 同时实现了支持比较的 PartialOrd Trait 支持打印的 Display Trait 时,Pair<T> 才会实现 cmp_display 方法。

By using a trait bound with an impl block that uses generic type parameters, we can implement methods conditionally for types that implement the specified traits. For example, the type Pair<T> in Listing 10-15 always implements the new function to return a new instance of Pair<T> (recall from the “Method Syntax” section of Chapter 5 that Self is a type alias for the type of the impl block, which in this case is Pair<T>). But in the next impl block, Pair<T> only implements the cmp_display method if its inner type T implements the PartialOrd trait that enables comparison and the Display trait that enables printing.

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 块看起来类似于这段代码:

We can also conditionally implement a trait for any type that implements another trait. Implementations of a trait on any type that satisfies the trait bounds are called blanket implementations and are used extensively in the Rust standard library. For example, the standard library implements the ToString trait on any type that implements the Display trait. The impl block in the standard library looks similar to this code:

impl<T: Display> ToString for T {
    // --snip--
}

因为标准库具有这种全面实现,所以我们可以在任何实现了 Display Trait 的类型上调用由 ToString Trait 定义的 to_string 方法。例如,我们可以像这样将整数转换为它们对应的 String 值,因为整数实现了 Display

Because the standard library has this blanket implementation, we can call the to_string method defined by the ToString trait on any type that implements the Display trait. For example, we can turn integers into their corresponding String values like this because integers implement Display:

#![allow(unused)]
fn main() {
let s = 3.to_string();
}

全面实现会出现在 Trait 文档的 “Implementors” 部分。

Blanket implementations appear in the documentation for the trait in the “Implementors” section.

Trait 和 Trait bound 让我们能够编写使用泛型类型参数的代码以减少重复,同时也能向编译器指定我们希望泛型类型具有特定行为。编译器随后可以利用 Trait bound 信息来检查我们的代码所使用的所有具体类型是否都提供了正确的行为。在动态类型语言中,如果我们对一个没有定义该方法的类型调用该方法,我们会在运行时得到错误。但 Rust 将这些错误移到了编译时,这样我们就必须在代码运行之前修复这些问题。此外,我们不需要编写在运行时检查行为的代码,因为我们已经在编译时检查过了。这样做既提高了性能,又无需放弃泛型的灵活性。

Traits and trait bounds let us write code that uses generic type parameters to reduce duplication but also specify to the compiler that we want the generic type to have particular behavior. The compiler can then use the trait bound information to check that all the concrete types used with our code provide the correct behavior. In dynamically typed languages, we would get an error at runtime if we called a method on a type that didn’t define the method. But Rust moves these errors to compile time so that we’re forced to fix the problems before our code is even able to run. Additionally, we don’t have to write code that checks for behavior at runtime, because we’ve already checked at compile time. Doing so improves performance without having to give up the flexibility of generics.