|
|
@ -2,7 +2,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
> [ch10-02-traits.md](https://github.com/rust-lang/book/blob/main/src/ch10-02-traits.md)
|
|
|
|
> [ch10-02-traits.md](https://github.com/rust-lang/book/blob/main/src/ch10-02-traits.md)
|
|
|
|
> <br>
|
|
|
|
> <br>
|
|
|
|
> commit 34b403864ad9c5e27b00b7cc4a6893804ef5b989
|
|
|
|
> commit 3c2ca8528c3b92b7d30e73f2e8a1b84b2f68b0c8
|
|
|
|
|
|
|
|
|
|
|
|
*trait* 告诉 Rust 编译器某个特定类型拥有可能与其他类型共享的功能。可以通过 trait 以一种抽象的方式定义共享的行为。可以使用 *trait bounds* 指定泛型是任何拥有特定行为的类型。
|
|
|
|
*trait* 告诉 Rust 编译器某个特定类型拥有可能与其他类型共享的功能。可以通过 trait 以一种抽象的方式定义共享的行为。可以使用 *trait bounds* 指定泛型是任何拥有特定行为的类型。
|
|
|
|
|
|
|
|
|
|
|
@ -14,19 +14,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
例如,这里有多个存放了不同类型和属性文本的结构体:结构体 `NewsArticle` 用于存放发生于世界各地的新闻故事,而结构体 `Tweet` 最多只能存放 280 个字符的内容,以及像是否转推或是否是对推友的回复这样的元数据。
|
|
|
|
例如,这里有多个存放了不同类型和属性文本的结构体:结构体 `NewsArticle` 用于存放发生于世界各地的新闻故事,而结构体 `Tweet` 最多只能存放 280 个字符的内容,以及像是否转推或是否是对推友的回复这样的元数据。
|
|
|
|
|
|
|
|
|
|
|
|
我们想要创建一个多媒体聚合库用来显示可能储存在 `NewsArticle` 或 `Tweet` 实例中的数据的总结。每一个结构体都需要的行为是他们是能够被总结的,这样的话就可以调用实例的 `summarize` 方法来请求总结。示例 10-12 中展示了一个表现这个概念的 `Summary` trait 的定义:
|
|
|
|
我们想要创建一个名为 `aggregator` 的多媒体聚合库用来显示可能储存在 `NewsArticle` 或 `Tweet` 实例中的数据的总结。每一个结构体都需要的行为是他们是能够被总结的,这样的话就可以调用实例的 `summarize` 方法来请求总结。示例 10-12 中展示了一个表现这个概念的公有 `Summary` trait 的定义:
|
|
|
|
|
|
|
|
|
|
|
|
<span class="filename">文件名: src/lib.rs</span>
|
|
|
|
<span class="filename">文件名: src/lib.rs</span>
|
|
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
```rust,noplayground
|
|
|
|
pub trait Summary {
|
|
|
|
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-12/src/lib.rs}}
|
|
|
|
fn summarize(&self) -> String;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
<span class="caption">示例 10-12:`Summary` trait 定义,它包含由 `summarize` 方法提供的行为</span>
|
|
|
|
<span class="caption">示例 10-12:`Summary` trait 定义,它包含由 `summarize` 方法提供的行为</span>
|
|
|
|
|
|
|
|
|
|
|
|
这里使用 `trait` 关键字来声明一个 trait,后面是 trait 的名字,在这个例子中是 `Summary`。在大括号中声明描述实现这个 trait 的类型所需要的行为的方法签名,在这个例子中是 `fn summarize(&self) -> String`。
|
|
|
|
这里使用 `trait` 关键字来声明一个 trait,后面是 trait 的名字,在这个例子中是 `Summary`。我们也声明 crate 为 `pub` 以便依赖这个 crate 的也可以使用这个 crate,正如我们见过的一些示例一样。在大括号中声明描述实现这个 trait 的类型所需要的行为的方法签名,在这个例子中是 `fn summarize(&self) -> String`。
|
|
|
|
|
|
|
|
|
|
|
|
在方法签名后跟分号,而不是在大括号中提供其实现。接着每一个实现这个 trait 的类型都需要提供其自定义行为的方法体,编译器也会确保任何实现 `Summary` trait 的类型都拥有与这个签名的定义完全一致的 `summarize` 方法。
|
|
|
|
在方法签名后跟分号,而不是在大括号中提供其实现。接着每一个实现这个 trait 的类型都需要提供其自定义行为的方法体,编译器也会确保任何实现 `Summary` trait 的类型都拥有与这个签名的定义完全一致的 `summarize` 方法。
|
|
|
|
|
|
|
|
|
|
|
@ -34,64 +32,27 @@ trait 体中可以有多个方法:一行一个方法签名且都以分号结
|
|
|
|
|
|
|
|
|
|
|
|
### 为类型实现 trait
|
|
|
|
### 为类型实现 trait
|
|
|
|
|
|
|
|
|
|
|
|
现在我们定义了 `Summary` trait,接着就可以在多媒体聚合库中需要拥有这个行为的类型上实现它了。示例 10-13 中展示了 `NewsArticle` 结构体上 `Summary` trait 的一个实现,它使用标题、作者和创建的位置作为 `summarize` 的返回值。对于 `Tweet` 结构体,我们选择将 `summarize` 定义为用户名后跟推文的全部文本作为返回值,并假设推文内容已经被限制为 280 字符以内。
|
|
|
|
现在我们定义了 `Summary` trait 的签名,接着就可以在多媒体聚合库中实现这个类型了。示例 10-13 中展示了 `NewsArticle` 结构体上 `Summary` trait 的一个实现,它使用标题、作者和创建的位置作为 `summarize` 的返回值。对于 `Tweet` 结构体,我们选择将 `summarize` 定义为用户名后跟推文的全部文本作为返回值,并假设推文内容已经被限制为 280 字符以内。
|
|
|
|
|
|
|
|
|
|
|
|
<span class="filename">文件名: src/lib.rs</span>
|
|
|
|
<span class="filename">文件名: src/lib.rs</span>
|
|
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
```rust,noplayground
|
|
|
|
# pub trait Summary {
|
|
|
|
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-13/src/lib.rs:here}}
|
|
|
|
# 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)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
<span class="caption">示例 10-13:在 `NewsArticle` 和 `Tweet` 类型上实现 `Summary` trait</span>
|
|
|
|
<span class="caption">示例 10-13:在 `NewsArticle` 和 `Tweet` 类型上实现 `Summary` trait</span>
|
|
|
|
|
|
|
|
|
|
|
|
在类型上实现 trait 类似于实现与 trait 无关的方法。区别在于 `impl` 关键字之后,我们提供需要实现 trait 的名称,接着是 `for` 和需要实现 trait 的类型的名称。在 `impl` 块中,使用 trait 定义中的方法签名,不过不再后跟分号,而是需要在大括号中编写函数体来为特定类型实现 trait 方法所拥有的行为。
|
|
|
|
在类型上实现 trait 类似于实现与 trait 无关的方法。区别在于 `impl` 关键字之后,我们提供需要实现 trait 的名称,接着是 `for` 和需要实现 trait 的类型的名称。在 `impl` 块中,使用 trait 定义中的方法签名,不过不再后跟分号,而是需要在大括号中编写函数体来为特定类型实现 trait 方法所拥有的行为。
|
|
|
|
|
|
|
|
|
|
|
|
一旦实现了 trait,我们就可以用与 `NewsArticle` 和 `Tweet` 实例的非 trait 方法一样的方式调用 trait 方法了:
|
|
|
|
现在库在 `NewsArticle` 和 `Tweet` 上实现了`Summary` trait,crate 的用户可以像调用常规方法一样调用 `NewsArticle` 和 `Tweet` 实例的 trait 方法了。唯一的区别是 trait 必须和类型一起引入作用域以便使用额外的 trait 方法。这是一个二进制 crate 如何利用 `aggregator` 库 crate 的例子:
|
|
|
|
|
|
|
|
|
|
|
|
```rust,ignore
|
|
|
|
```rust,ignore
|
|
|
|
let tweet = Tweet {
|
|
|
|
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/no-listing-01-calling-trait-method/src/main.rs}}
|
|
|
|
username: String::from("horse_ebooks"),
|
|
|
|
|
|
|
|
content: String::from("of course, as you probably already know, people"),
|
|
|
|
|
|
|
|
reply: false,
|
|
|
|
|
|
|
|
retweet: false,
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
println!("1 new tweet: {}", tweet.summarize());
|
|
|
|
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
这会打印出 `1 new tweet: horse_ebooks: of course, as you probably already know, people`。
|
|
|
|
这会打印出 `1 new tweet: horse_ebooks: of course, as you probably already know, people`。
|
|
|
|
|
|
|
|
|
|
|
|
注意因为示例 10-13 中我们在相同的 *lib.rs* 里定义了 `Summary` trait 和 `NewsArticle` 与 `Tweet` 类型,所以他们是位于同一作用域的。如果这个 *lib.rs* 是对应 `aggregator` crate 的,而别人想要利用我们 crate 的功能为其自己的库作用域中的结构体实现 `Summary` trait。首先他们需要将 trait 引入作用域。这可以通过指定 `use aggregator::Summary;` 实现,这样就可以为其类型实现 `Summary` trait 了。`Summary` 还必须是公有 trait 使得其他 crate 可以实现它,这也是为什么实例 10-12 中将 `pub` 置于 `trait` 之前。
|
|
|
|
其他依赖 `aggregator` crate 的 crate 也可以将 `Summary` 引入作用域以便为其自己的类型实现该 trait。实现 trait 时需要注意的一个限制是,只有当至少一个 trait 或者要实现 trait 的类型位于 crate 的本地作用域时,才能为该类型实现 trait。例如,可以为 `aggregator` crate 的自定义类型 `Tweet` 实现如标准库中的 `Display` trait,这是因为 `Tweet` 类型位于 `aggregator` crate 本地的作用域中。类似地,也可以在 `aggregator` crate 中为 `Vec<T>` 实现 `Summary`,这是因为 `Summary` trait 位于 `aggregator` crate 本地作用域中。
|
|
|
|
|
|
|
|
|
|
|
|
实现 trait 时需要注意的一个限制是,只有当 trait 或者要实现 trait 的类型位于 crate 的本地作用域时,才能为该类型实现 trait。例如,可以为 `aggregator` crate 的自定义类型 `Tweet` 实现如标准库中的 `Display` trait,这是因为 `Tweet` 类型位于 `aggregator` crate 本地的作用域中。类似地,也可以在 `aggregator` crate 中为 `Vec<T>` 实现 `Summary`,这是因为 `Summary` trait 位于 `aggregator` crate 本地作用域中。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
但是不能为外部类型实现外部 trait。例如,不能在 `aggregator` crate 中为 `Vec<T>` 实现 `Display` trait。这是因为 `Display` 和 `Vec<T>` 都定义于标准库中,它们并不位于 `aggregator` crate 本地作用域中。这个限制是被称为 **相干性**(*coherence*) 的程序属性的一部分,或者更具体的说是 **孤儿规则**(*orphan rule*),其得名于不存在父类型。这条规则确保了其他人编写的代码不会破坏你代码,反之亦然。没有这条规则的话,两个 crate 可以分别对相同类型实现相同的 trait,而 Rust 将无从得知应该使用哪一个实现。
|
|
|
|
但是不能为外部类型实现外部 trait。例如,不能在 `aggregator` crate 中为 `Vec<T>` 实现 `Display` trait。这是因为 `Display` 和 `Vec<T>` 都定义于标准库中,它们并不位于 `aggregator` crate 本地作用域中。这个限制是被称为 **相干性**(*coherence*) 的程序属性的一部分,或者更具体的说是 **孤儿规则**(*orphan rule*),其得名于不存在父类型。这条规则确保了其他人编写的代码不会破坏你代码,反之亦然。没有这条规则的话,两个 crate 可以分别对相同类型实现相同的 trait,而 Rust 将无从得知应该使用哪一个实现。
|
|
|
|
|
|
|
|
|
|
|
@ -103,12 +64,8 @@ println!("1 new tweet: {}", tweet.summarize());
|
|
|
|
|
|
|
|
|
|
|
|
<span class="filename">文件名: src/lib.rs</span>
|
|
|
|
<span class="filename">文件名: src/lib.rs</span>
|
|
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
```rust,noplayground
|
|
|
|
pub trait Summary {
|
|
|
|
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-14/src/lib.rs:here}}
|
|
|
|
fn summarize(&self) -> String {
|
|
|
|
|
|
|
|
String::from("(Read more...)")
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
<span class="caption">示例 10-14:`Summary` trait 的定义,带有一个 `summarize` 方法的默认实现</span>
|
|
|
|
<span class="caption">示例 10-14:`Summary` trait 的定义,带有一个 `summarize` 方法的默认实现</span>
|
|
|
@ -118,15 +75,7 @@ pub trait Summary {
|
|
|
|
虽然我们不再直接为 `NewsArticle` 定义 `summarize` 方法了,但是我们提供了一个默认实现并且指定 `NewsArticle` 实现 `Summary` trait。因此,我们仍然可以对 `NewsArticle` 实例调用 `summarize` 方法,如下所示:
|
|
|
|
虽然我们不再直接为 `NewsArticle` 定义 `summarize` 方法了,但是我们提供了一个默认实现并且指定 `NewsArticle` 实现 `Summary` trait。因此,我们仍然可以对 `NewsArticle` 实例调用 `summarize` 方法,如下所示:
|
|
|
|
|
|
|
|
|
|
|
|
```rust,ignore
|
|
|
|
```rust,ignore
|
|
|
|
let article = NewsArticle {
|
|
|
|
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/no-listing-02-calling-default-impl/src/main.rs:here}}
|
|
|
|
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...)`。
|
|
|
|
这段代码会打印 `New article available! (Read more...)`。
|
|
|
@ -135,37 +84,20 @@ println!("New article available! {}", article.summarize());
|
|
|
|
|
|
|
|
|
|
|
|
默认实现允许调用相同 trait 中的其他方法,哪怕这些方法没有默认实现。如此,trait 可以提供很多有用的功能而只需要实现指定一小部分内容。例如,我们可以定义 `Summary` trait,使其具有一个需要实现的 `summarize_author` 方法,然后定义一个 `summarize` 方法,此方法的默认实现调用 `summarize_author` 方法:
|
|
|
|
默认实现允许调用相同 trait 中的其他方法,哪怕这些方法没有默认实现。如此,trait 可以提供很多有用的功能而只需要实现指定一小部分内容。例如,我们可以定义 `Summary` trait,使其具有一个需要实现的 `summarize_author` 方法,然后定义一个 `summarize` 方法,此方法的默认实现调用 `summarize_author` 方法:
|
|
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
```rust,noplayground
|
|
|
|
pub trait Summary {
|
|
|
|
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/no-listing-03-default-impl-calls-other-methods/src/lib.rs:here}}
|
|
|
|
fn summarize_author(&self) -> String;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn summarize(&self) -> String {
|
|
|
|
|
|
|
|
format!("(Read more from {}...)", self.summarize_author())
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
为了使用这个版本的 `Summary`,只需在实现 trait 时定义 `summarize_author` 即可:
|
|
|
|
为了使用这个版本的 `Summary`,只需在实现 trait 时定义 `summarize_author` 即可:
|
|
|
|
|
|
|
|
|
|
|
|
```rust,ignore
|
|
|
|
```rust,ignore
|
|
|
|
impl Summary for Tweet {
|
|
|
|
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/no-listing-03-default-impl-calls-other-methods/src/lib.rs:impl}}
|
|
|
|
fn summarize_author(&self) -> String {
|
|
|
|
|
|
|
|
format!("@{}", self.username)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
一旦定义了 `summarize_author`,我们就可以对 `Tweet` 结构体的实例调用 `summarize` 了,而 `summarize` 的默认实现会调用我们提供的 `summarize_author` 定义。因为实现了 `summarize_author`,`Summary` trait 就提供了 `summarize` 方法的功能,且无需编写更多的代码。
|
|
|
|
一旦定义了 `summarize_author`,我们就可以对 `Tweet` 结构体的实例调用 `summarize` 了,而 `summarize` 的默认实现会调用我们提供的 `summarize_author` 定义。因为实现了 `summarize_author`,`Summary` trait 就提供了 `summarize` 方法的功能,且无需编写更多的代码。
|
|
|
|
|
|
|
|
|
|
|
|
```rust,ignore
|
|
|
|
```rust,ignore
|
|
|
|
let tweet = Tweet {
|
|
|
|
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/no-listing-03-default-impl-calls-other-methods/src/main.rs:here}}
|
|
|
|
username: String::from("horse_ebooks"),
|
|
|
|
|
|
|
|
content: String::from("of course, as you probably already know, people"),
|
|
|
|
|
|
|
|
reply: false,
|
|
|
|
|
|
|
|
retweet: false,
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
println!("1 new tweet: {}", tweet.summarize());
|
|
|
|
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
这会打印出 `1 new tweet: (Read more from @horse_ebooks...)`。
|
|
|
|
这会打印出 `1 new tweet: (Read more from @horse_ebooks...)`。
|
|
|
@ -179,9 +111,7 @@ println!("1 new tweet: {}", tweet.summarize());
|
|
|
|
例如在示例 10-13 中为 `NewsArticle` 和 `Tweet` 类型实现了 `Summary` trait。我们可以定义一个函数 `notify` 来调用其参数 `item` 上的 `summarize` 方法,该参数是实现了 `Summary` trait 的某种类型。为此可以使用 `impl Trait` 语法,像这样:
|
|
|
|
例如在示例 10-13 中为 `NewsArticle` 和 `Tweet` 类型实现了 `Summary` trait。我们可以定义一个函数 `notify` 来调用其参数 `item` 上的 `summarize` 方法,该参数是实现了 `Summary` trait 的某种类型。为此可以使用 `impl Trait` 语法,像这样:
|
|
|
|
|
|
|
|
|
|
|
|
```rust,ignore
|
|
|
|
```rust,ignore
|
|
|
|
pub fn notify(item: impl Summary) {
|
|
|
|
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/no-listing-04-traits-as-parameters/src/lib.rs:here}}
|
|
|
|
println!("Breaking news! {}", item.summarize());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
对于 `item` 参数,我们指定了 `impl` 关键字和 trait 名称,而不是具体的类型。该参数支持任何实现了指定 trait 的类型。在 `notify` 函数体中,可以调用任何来自 `Summary` trait 的方法,比如 `summarize`。我们可以传递任何 `NewsArticle` 或 `Tweet` 的实例来调用 `notify`。任何用其它如 `String` 或 `i32` 的类型调用该函数的代码都不能编译,因为它们没有实现 `Summary`。
|
|
|
|
对于 `item` 参数,我们指定了 `impl` 关键字和 trait 名称,而不是具体的类型。该参数支持任何实现了指定 trait 的类型。在 `notify` 函数体中,可以调用任何来自 `Summary` trait 的方法,比如 `summarize`。我们可以传递任何 `NewsArticle` 或 `Tweet` 的实例来调用 `notify`。任何用其它如 `String` 或 `i32` 的类型调用该函数的代码都不能编译,因为它们没有实现 `Summary`。
|
|
|
@ -191,7 +121,7 @@ pub fn notify(item: impl Summary) {
|
|
|
|
`impl Trait` 语法适用于直观的例子,它实际上是一种较长形式语法的语法糖。我们称为 *trait bound*,它看起来像:
|
|
|
|
`impl Trait` 语法适用于直观的例子,它实际上是一种较长形式语法的语法糖。我们称为 *trait bound*,它看起来像:
|
|
|
|
|
|
|
|
|
|
|
|
```rust,ignore
|
|
|
|
```rust,ignore
|
|
|
|
pub fn notify<T: Summary>(item: T) {
|
|
|
|
pub fn notify<T: Summary>(item: &T) {
|
|
|
|
println!("Breaking news! {}", item.summarize());
|
|
|
|
println!("Breaking news! {}", item.summarize());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
```
|
|
|
@ -201,13 +131,13 @@ pub fn notify<T: Summary>(item: T) {
|
|
|
|
`impl Trait` 很方便,适用于短小的例子。trait bound 则适用于更复杂的场景。例如,可以获取两个实现了 `Summary` 的参数。使用 `impl Trait` 的语法看起来像这样:
|
|
|
|
`impl Trait` 很方便,适用于短小的例子。trait bound 则适用于更复杂的场景。例如,可以获取两个实现了 `Summary` 的参数。使用 `impl Trait` 的语法看起来像这样:
|
|
|
|
|
|
|
|
|
|
|
|
```rust,ignore
|
|
|
|
```rust,ignore
|
|
|
|
pub fn notify(item1: impl Summary, item2: impl Summary) {
|
|
|
|
pub fn notify(item1: &impl Summary, item2: &impl Summary) {
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
这适用于 `item1` 和 `item2` 允许是不同类型的情况(只要它们都实现了 `Summary`)。不过如果你希望强制它们都是相同类型呢?这只有在使用 trait bound 时才有可能:
|
|
|
|
这适用于 `item1` 和 `item2` 允许是不同类型的情况(只要它们都实现了 `Summary`)。不过如果你希望强制它们都是相同类型呢?这只有在使用 trait bound 时才有可能:
|
|
|
|
|
|
|
|
|
|
|
|
```rust,ignore
|
|
|
|
```rust,ignore
|
|
|
|
pub fn notify<T: Summary>(item1: T, item2: T) {
|
|
|
|
pub fn notify<T: Summary>(item1: &T, item2: &T) {
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
泛型 `T` 被指定为 `item1` 和 `item2` 的参数限制,如此传递给参数 `item1` 和 `item2` 值的具体类型必须一致。
|
|
|
|
泛型 `T` 被指定为 `item1` 和 `item2` 的参数限制,如此传递给参数 `item1` 和 `item2` 值的具体类型必须一致。
|
|
|
@ -217,13 +147,13 @@ pub fn notify<T: Summary>(item1: T, item2: T) {
|
|
|
|
如果 `notify` 需要显示 `item` 的格式化形式,同时也要使用 `summarize` 方法,那么 `item` 就需要同时实现两个不同的 trait:`Display` 和 `Summary`。这可以通过 `+` 语法实现:
|
|
|
|
如果 `notify` 需要显示 `item` 的格式化形式,同时也要使用 `summarize` 方法,那么 `item` 就需要同时实现两个不同的 trait:`Display` 和 `Summary`。这可以通过 `+` 语法实现:
|
|
|
|
|
|
|
|
|
|
|
|
```rust,ignore
|
|
|
|
```rust,ignore
|
|
|
|
pub fn notify(item: impl Summary + Display) {
|
|
|
|
pub fn notify(item: &(impl Summary + Display)) {
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
`+` 语法也适用于泛型的 trait bound:
|
|
|
|
`+` 语法也适用于泛型的 trait bound:
|
|
|
|
|
|
|
|
|
|
|
|
```rust,ignore
|
|
|
|
```rust,ignore
|
|
|
|
pub fn notify<T: Summary + Display>(item: T) {
|
|
|
|
pub fn notify<T: Summary + Display>(item: &T) {
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
通过指定这两个 trait bound,`notify` 的函数体可以调用 `summarize` 并使用 `{}` 来格式化 `item`。
|
|
|
|
通过指定这两个 trait bound,`notify` 的函数体可以调用 `summarize` 并使用 `{}` 来格式化 `item`。
|
|
|
@ -233,13 +163,13 @@ pub fn notify<T: Summary + Display>(item: T) {
|
|
|
|
然而,使用过多的 trait bound 也有缺点。每个泛型有其自己的 trait bound,所以有多个泛型参数的函数在名称和参数列表之间会有很长的 trait bound 信息,这使得函数签名难以阅读。为此,Rust 有另一个在函数签名之后的 `where` 从句中指定 trait bound 的语法。所以除了这么写:
|
|
|
|
然而,使用过多的 trait bound 也有缺点。每个泛型有其自己的 trait bound,所以有多个泛型参数的函数在名称和参数列表之间会有很长的 trait bound 信息,这使得函数签名难以阅读。为此,Rust 有另一个在函数签名之后的 `where` 从句中指定 trait bound 的语法。所以除了这么写:
|
|
|
|
|
|
|
|
|
|
|
|
```rust,ignore
|
|
|
|
```rust,ignore
|
|
|
|
fn some_function<T: Display + Clone, U: Clone + Debug>(t: T, u: U) -> i32 {
|
|
|
|
fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
还可以像这样使用 `where` 从句:
|
|
|
|
还可以像这样使用 `where` 从句:
|
|
|
|
|
|
|
|
|
|
|
|
```rust,ignore
|
|
|
|
```rust,ignore
|
|
|
|
fn some_function<T, U>(t: T, u: U) -> i32
|
|
|
|
fn some_function<T, U>(t: &T, u: &U) -> i32
|
|
|
|
where T: Display + Clone,
|
|
|
|
where T: Display + Clone,
|
|
|
|
U: Clone + Debug
|
|
|
|
U: Clone + Debug
|
|
|
|
{
|
|
|
|
{
|
|
|
@ -252,14 +182,7 @@ fn some_function<T, U>(t: T, u: U) -> i32
|
|
|
|
也可以在返回值中使用 `impl Trait` 语法,来返回实现了某个 trait 的类型:
|
|
|
|
也可以在返回值中使用 `impl Trait` 语法,来返回实现了某个 trait 的类型:
|
|
|
|
|
|
|
|
|
|
|
|
```rust,ignore
|
|
|
|
```rust,ignore
|
|
|
|
fn returns_summarizable() -> impl Summary {
|
|
|
|
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/no-listing-05-returning-impl-trait/src/lib.rs:here}}
|
|
|
|
Tweet {
|
|
|
|
|
|
|
|
username: String::from("horse_ebooks"),
|
|
|
|
|
|
|
|
content: String::from("of course, as you probably already know, people"),
|
|
|
|
|
|
|
|
reply: false,
|
|
|
|
|
|
|
|
retweet: false,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
通过使用 `impl Summary` 作为返回值类型,我们指定了 `returns_summarizable` 函数返回某个实现了 `Summary` trait 的类型,但是不确定其具体的类型。在这个例子中 `returns_summarizable` 返回了一个 `Tweet`,不过调用方并不知情。
|
|
|
|
通过使用 `impl Summary` 作为返回值类型,我们指定了 `returns_summarizable` 函数返回某个实现了 `Summary` trait 的类型,但是不确定其具体的类型。在这个例子中 `returns_summarizable` 返回了一个 `Tweet`,不过调用方并不知情。
|
|
|
@ -269,24 +192,7 @@ fn returns_summarizable() -> impl Summary {
|
|
|
|
不过这只适用于返回单一类型的情况。例如,这段代码的返回值类型指定为返回 `impl Summary`,但是返回了 `NewsArticle` 或 `Tweet` 就行不通:
|
|
|
|
不过这只适用于返回单一类型的情况。例如,这段代码的返回值类型指定为返回 `impl Summary`,但是返回了 `NewsArticle` 或 `Tweet` 就行不通:
|
|
|
|
|
|
|
|
|
|
|
|
```rust,ignore,does_not_compile
|
|
|
|
```rust,ignore,does_not_compile
|
|
|
|
fn returns_summarizable(switch: bool) -> impl Summary {
|
|
|
|
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/no-listing-06-impl-trait-returns-one-type/src/lib.rs:here}}
|
|
|
|
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 {
|
|
|
|
|
|
|
|
Tweet {
|
|
|
|
|
|
|
|
username: String::from("horse_ebooks"),
|
|
|
|
|
|
|
|
content: String::from("of course, as you probably already know, people"),
|
|
|
|
|
|
|
|
reply: false,
|
|
|
|
|
|
|
|
retweet: false,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
这里尝试返回 `NewsArticle` 或 `Tweet`。这不能编译,因为 `impl Trait` 工作方式的限制。第十七章的 [“为使用不同类型的值而设计的 trait 对象”][using-trait-objects-that-allow-for-values-of-different-types] 部分会介绍如何编写这样一个函数。
|
|
|
|
这里尝试返回 `NewsArticle` 或 `Tweet`。这不能编译,因为 `impl Trait` 工作方式的限制。第十七章的 [“为使用不同类型的值而设计的 trait 对象”][using-trait-objects-that-allow-for-values-of-different-types] 部分会介绍如何编写这样一个函数。
|
|
|
@ -295,42 +201,20 @@ fn returns_summarizable(switch: bool) -> impl Summary {
|
|
|
|
|
|
|
|
|
|
|
|
现在你知道了如何使用泛型参数 trait bound 来指定所需的行为。让我们回到实例 10-5 修复使用泛型类型参数的 `largest` 函数定义!回顾一下,最后尝试编译代码时出现的错误是:
|
|
|
|
现在你知道了如何使用泛型参数 trait bound 来指定所需的行为。让我们回到实例 10-5 修复使用泛型类型参数的 `largest` 函数定义!回顾一下,最后尝试编译代码时出现的错误是:
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
```console
|
|
|
|
error[E0369]: binary operation `>` cannot be applied to type `T`
|
|
|
|
{{#include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-05/output.txt}}
|
|
|
|
--> src/main.rs:5:12
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5 | if item > largest {
|
|
|
|
|
|
|
|
| ^^^^^^^^^^^^^^
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
= note: an implementation of `std::cmp::PartialOrd` might be missing for `T`
|
|
|
|
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
在 `largest` 函数体中我们想要使用大于运算符(`>`)比较两个 `T` 类型的值。这个运算符被定义为标准库中 trait `std::cmp::PartialOrd` 的一个默认方法。所以需要在 `T` 的 trait bound 中指定 `PartialOrd`,这样 `largest` 函数可以用于任何可以比较大小的类型的 slice。因为 `PartialOrd` 位于 prelude 中所以并不需要手动将其引入作用域。将 `largest` 的签名修改为如下:
|
|
|
|
在 `largest` 函数体中我们想要使用大于运算符(`>`)比较两个 `T` 类型的值。这个运算符被定义为标准库中 trait `std::cmp::PartialOrd` 的一个默认方法。所以需要在 `T` 的 trait bound 中指定 `PartialOrd`,这样 `largest` 函数可以用于任何可以比较大小的类型的 slice。因为 `PartialOrd` 位于 prelude 中所以并不需要手动将其引入作用域。将 `largest` 的签名修改为如下:
|
|
|
|
|
|
|
|
|
|
|
|
```rust,ignore
|
|
|
|
```rust,ignore
|
|
|
|
fn largest<T: PartialOrd>(list: &[T]) -> T {
|
|
|
|
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/no-listing-07-fixing-listing-10-05/src/main.rs:here}}
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
但是如果编译代码的话,会出现一些不同的错误:
|
|
|
|
但是如果编译代码的话,会出现一些不同的错误:
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
```console
|
|
|
|
error[E0508]: cannot move out of type `[T]`, a non-copy slice
|
|
|
|
{{#include ../listings/ch10-generic-types-traits-and-lifetimes/no-listing-07-fixing-listing-10-05/output.txt}}
|
|
|
|
--> src/main.rs:2:23
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2 | let mut largest = list[0];
|
|
|
|
|
|
|
|
| ^^^^^^^
|
|
|
|
|
|
|
|
| |
|
|
|
|
|
|
|
|
| cannot move out of here
|
|
|
|
|
|
|
|
| help: consider using a reference instead: `&list[0]`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
error[E0507]: cannot move out of borrowed content
|
|
|
|
|
|
|
|
--> src/main.rs:4:9
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4 | for &item in list.iter() {
|
|
|
|
|
|
|
|
| ^----
|
|
|
|
|
|
|
|
| ||
|
|
|
|
|
|
|
|
| |hint: to prevent move, use `ref item` or `ref mut item`
|
|
|
|
|
|
|
|
| cannot move out of borrowed content
|
|
|
|
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
错误的核心是 `cannot move out of type [T], a non-copy slice`,对于非泛型版本的 `largest` 函数,我们只尝试了寻找最大的 `i32` 和 `char`。正如第四章 [“只在栈上的数据:拷贝”][stack-only-data-copy] 部分讨论过的,像 `i32` 和 `char` 这样的类型是已知大小的并可以储存在栈上,所以他们实现了 `Copy` trait。当我们将 `largest` 函数改成使用泛型后,现在 `list` 参数的类型就有可能是没有实现 `Copy` trait 的。这意味着我们可能不能将 `list[0]` 的值移动到 `largest` 变量中,这导致了上面的错误。
|
|
|
|
错误的核心是 `cannot move out of type [T], a non-copy slice`,对于非泛型版本的 `largest` 函数,我们只尝试了寻找最大的 `i32` 和 `char`。正如第四章 [“只在栈上的数据:拷贝”][stack-only-data-copy] 部分讨论过的,像 `i32` 和 `char` 这样的类型是已知大小的并可以储存在栈上,所以他们实现了 `Copy` trait。当我们将 `largest` 函数改成使用泛型后,现在 `list` 参数的类型就有可能是没有实现 `Copy` trait 的。这意味着我们可能不能将 `list[0]` 的值移动到 `largest` 变量中,这导致了上面的错误。
|
|
|
@ -340,67 +224,21 @@ error[E0507]: cannot move out of borrowed content
|
|
|
|
<span class="filename">文件名: src/main.rs</span>
|
|
|
|
<span class="filename">文件名: src/main.rs</span>
|
|
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
```rust
|
|
|
|
fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
|
|
|
|
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-15/src/main.rs}}
|
|
|
|
let mut largest = list[0];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for &item in list.iter() {
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
<span class="caption">示例 10-15:一个可以用于任何实现了 `PartialOrd` 和 `Copy` trait 的泛型的 `largest` 函数</span>
|
|
|
|
<span class="caption">示例 10-15:一个可以用于任何实现了 `PartialOrd` 和 `Copy` trait 的泛型的 `largest` 函数</span>
|
|
|
|
|
|
|
|
|
|
|
|
如果并不希望限制 `largest` 函数只能用于实现了 `Copy` trait 的类型,我们可以在 `T` 的 trait bounds 中指定 `Clone` 而不是 `Copy`。并克隆 slice 的每一个值使得 `largest` 函数拥有其所有权。使用 `clone` 函数意味着对于类似 `String` 这样拥有堆上数据的类型,会潜在的分配更多堆上空间,而堆分配在涉及大量数据时可能会相当缓慢。
|
|
|
|
如果并不希望限制 `largest` 函数只能用于实现了 `Copy` trait 的类型,我们可以在 `T` 的 trait bounds 中指定 `Clone` 而不是 `Copy`。并克隆 slice 的每一个值使得 `largest` 函数拥有其所有权。使用 `clone` 函数意味着对于类似 `String` 这样拥有堆上数据的类型,会潜在的分配更多堆上空间,而堆分配在涉及大量数据时可能会相当缓慢。
|
|
|
|
|
|
|
|
|
|
|
|
另一种 `largest` 的实现方式是返回在 slice 中 `T` 值的引用。如果我们将函数返回值从 `T` 改为 `&T` 并改变函数体使其能够返回一个引用,我们将不需要任何 `Clone` 或 `Copy` 的 trait bounds 而且也不会有任何的堆分配。尝试自己实现这种替代解决方式吧!
|
|
|
|
另一种 `largest` 的实现方式是返回在 slice 中 `T` 值的引用。如果我们将函数返回值从 `T` 改为 `&T` 并改变函数体使其能够返回一个引用,我们将不需要任何 `Clone` 或 `Copy` 的 trait bounds 而且也不会有任何的堆分配。尝试自己实现这种替代解决方式吧!如果你无法摆脱与生命周期有关的错误,请继续阅读:接下来的 “生命周期与引用有效性” 部分会详细的说明,不过声明周期对于解决这些挑战来说并不是必须的。
|
|
|
|
|
|
|
|
|
|
|
|
### 使用 trait bound 有条件地实现方法
|
|
|
|
### 使用 trait bound 有条件地实现方法
|
|
|
|
|
|
|
|
|
|
|
|
通过使用带有 trait bound 的泛型参数的 `impl` 块,可以有条件地只为那些实现了特定 trait 的类型实现方法。例如,示例 10-16 中的类型 `Pair<T>` 总是实现了 `new` 方法,不过只有那些为 `T` 类型实现了 `PartialOrd` trait (来允许比较) **和** `Display` trait (来启用打印)的 `Pair<T>` 才会实现 `cmp_display` 方法:
|
|
|
|
通过使用带有 trait bound 的泛型参数的 `impl` 块,可以有条件地只为那些实现了特定 trait 的类型实现方法。例如,示例 10-16 中的类型 `Pair<T>` 总是实现了 `new` 方法并返回一个 `Pair<T>` 的实例(回忆一下第五章的 ["定义方法"][methods] 部分,`Self` 是一个 `impl` 块类型的类型别名(type alias),在这里是 `Pair<T>`)。不过在下一个 `impl` 块中,只有那些为 `T` 类型实现了 `PartialOrd` trait (来允许比较) **和** `Display` trait (来启用打印)的 `Pair<T>` 才会实现 `cmp_display` 方法:
|
|
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
```rust,noplayground
|
|
|
|
use std::fmt::Display;
|
|
|
|
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-16/src/lib.rs}}
|
|
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
<span class="caption">示例 10-16:根据 trait bound 在泛型上有条件的实现方法</span>
|
|
|
|
<span class="caption">示例 10-16:根据 trait bound 在泛型上有条件的实现方法</span>
|
|
|
@ -426,6 +264,7 @@ trait 和 trait bound 让我们使用泛型类型参数来减少重复,并仍
|
|
|
|
这里还有一种泛型,我们一直在使用它甚至都没有察觉它的存在,这就是 **生命周期**(*lifetimes*)。不同于其他泛型帮助我们确保类型拥有期望的行为,生命周期则有助于确保引用在我们需要他们的时候一直有效。让我们学习生命周期是如何做到这些的。
|
|
|
|
这里还有一种泛型,我们一直在使用它甚至都没有察觉它的存在,这就是 **生命周期**(*lifetimes*)。不同于其他泛型帮助我们确保类型拥有期望的行为,生命周期则有助于确保引用在我们需要他们的时候一直有效。让我们学习生命周期是如何做到这些的。
|
|
|
|
|
|
|
|
|
|
|
|
[stack-only-data-copy]:
|
|
|
|
[stack-only-data-copy]:
|
|
|
|
ch04-01-what-is-ownership.html#stack-only-data-copy
|
|
|
|
ch04-01-what-is-ownership.html#只在栈上的数据拷贝
|
|
|
|
[using-trait-objects-that-allow-for-values-of-different-types]:
|
|
|
|
[using-trait-objects-that-allow-for-values-of-different-types]:
|
|
|
|
ch17-02-trait-objects.html#using-trait-objects-that-allow-for-values-of-different-types
|
|
|
|
ch17-02-trait-objects.html#为使用不同类型的值而设计的-trait-对象
|
|
|
|
|
|
|
|
[methods]: ch05-03-method-syntax.html#定义方法
|
|
|
|