|
|
|
@ -1,6 +1,6 @@
|
|
|
|
|
## 高级 trait
|
|
|
|
|
|
|
|
|
|
> [ch20-03-advanced-traits.md](https://github.com/rust-lang/book/blob/main/src/ch20-03-advanced-traits.md)
|
|
|
|
|
> [ch20-02-advanced-traits.md](https://github.com/rust-lang/book/blob/main/src/ch20-02-advanced-traits.md)
|
|
|
|
|
> <br>
|
|
|
|
|
> commit 95e931170404cb98d476b19017cbbdbc00d0834d
|
|
|
|
|
|
|
|
|
@ -12,13 +12,13 @@
|
|
|
|
|
|
|
|
|
|
我们之前提到,本章所描述的大部分内容都较少使用。关联类型则比较适中;它们比本书其他的内容要少见,不过比本章中的很多内容要更常见。
|
|
|
|
|
|
|
|
|
|
一个带有关联类型的 trait 的例子是标准库提供的 `Iterator` trait。它有一个叫做 `Item` 的关联类型来替代遍历的值的类型。`Iterator` trait 的定义如示例 19-12 所示:
|
|
|
|
|
一个带有关联类型的 trait 的例子是标准库提供的 `Iterator` trait。它有一个叫做 `Item` 的关联类型来替代遍历的值的类型。`Iterator` trait 的定义如示例 20-13 所示:
|
|
|
|
|
|
|
|
|
|
```rust,noplayground
|
|
|
|
|
{{#rustdoc_include ../listings/ch20-advanced-features/listing-20-12/src/lib.rs}}
|
|
|
|
|
{{#rustdoc_include ../listings/ch20-advanced-features/listing-20-13/src/lib.rs}}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
<span class="caption">示例 19-12: `Iterator` trait 的定义中带有关联类型 `Item`</span>
|
|
|
|
|
<span class="caption">示例 20-13: `Iterator` trait 的定义中带有关联类型 `Item`</span>
|
|
|
|
|
|
|
|
|
|
`Item` 是一个占位符类型,同时 `next` 方法的定义表明它返回 `Option<Self::Item>` 类型的值。这个 trait 的实现者会指定 `Item` 的具体类型,无论实现者指定何种类型,`next` 方法都会返回一个包含了此具体类型值的 `Option`。
|
|
|
|
|
|
|
|
|
@ -31,17 +31,17 @@
|
|
|
|
|
{{#rustdoc_include ../listings/ch20-advanced-features/no-listing-22-iterator-on-counter/src/lib.rs:ch19}}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
这个语法类似于泛型。那么为什么 `Iterator` trait 不像下面示例 19-13 那样,使用泛型来定义呢?
|
|
|
|
|
这个语法类似于泛型。那么为什么 `Iterator` trait 不像下面示例 20-13 那样,使用泛型来定义呢?
|
|
|
|
|
|
|
|
|
|
```rust,noplayground
|
|
|
|
|
{{#rustdoc_include ../listings/ch20-advanced-features/listing-20-13/src/lib.rs}}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
<span class="caption">示例 19-13: 一个使用泛型的 `Iterator` trait 假想定义</span>
|
|
|
|
|
<span class="caption">示例 20-13: 一个使用泛型的 `Iterator` trait 假想定义</span>
|
|
|
|
|
|
|
|
|
|
区别在于当如示例 19-13 那样使用泛型时,则不得不在每一个实现中标注类型。这是因为我们也可以实现为 `Iterator<String> for Counter`,或任何其他类型,这样就可以有多个 `Counter` 的 `Iterator` 的实现。换句话说,当 trait 有泛型参数时,可以多次实现这个 trait,每次需改变泛型参数的具体类型。接着当使用 `Counter` 的 `next` 方法时,必须提供类型注解来表明希望使用 `Iterator` 的哪一个实现。
|
|
|
|
|
区别在于当如示例 20-13 那样使用泛型时,则不得不在每一个实现中标注类型。这是因为我们也可以实现为 `Iterator<String> for Counter`,或任何其他类型,这样就可以有多个 `Counter` 的 `Iterator` 的实现。换句话说,当 trait 有泛型参数时,可以多次实现这个 trait,每次需改变泛型参数的具体类型。接着当使用 `Counter` 的 `next` 方法时,必须提供类型注解来表明希望使用 `Iterator` 的哪一个实现。
|
|
|
|
|
|
|
|
|
|
有了关联类型,在实现时就无需标注类型,因为不能多次实现这个 trait。对于示例 19-12 使用关联类型的定义,我们只能选择一次 `Item` 会是什么类型,因为只能有一个 `impl Iterator for Counter`。当调用 `Counter` 的 `next` 时不必每次指定我们需要 `u32` 值的迭代器。
|
|
|
|
|
有了关联类型,在实现时就无需标注类型,因为不能多次实现这个 trait。对于示例 20-12 使用关联类型的定义,我们只能选择一次 `Item` 会是什么类型,因为只能有一个 `impl Iterator for Counter`。当调用 `Counter` 的 `next` 时不必每次指定我们需要 `u32` 值的迭代器。
|
|
|
|
|
|
|
|
|
|
关联类型也会成为 trait 契约的一部分:trait 的实现必须提供一个类型来替代关联类型占位符。关联类型通常以它的用途来命名,并且我们最好在 API 文档中为关联类型编写文档。
|
|
|
|
|
|
|
|
|
@ -51,15 +51,15 @@
|
|
|
|
|
|
|
|
|
|
这种情况的一个非常好的例子是使用 **运算符重载**(*Operator overloading*),这是指在特定情况下自定义运算符(比如 `+`)行为的操作。
|
|
|
|
|
|
|
|
|
|
Rust 并不允许创建自定义运算符或重载任意运算符,不过 `std::ops` 中所列出的运算符和相应的 trait 可以通过实现运算符相关 trait 来重载。例如,示例 19-14 中展示了如何在 `Point` 结构体上实现 `Add` trait 来重载 `+` 运算符,这样就可以将两个 `Point` 实例相加了:
|
|
|
|
|
Rust 并不允许创建自定义运算符或重载任意运算符,不过 `std::ops` 中所列出的运算符和相应的 trait 可以通过实现运算符相关 trait 来重载。例如,示例 20-14 中展示了如何在 `Point` 结构体上实现 `Add` trait 来重载 `+` 运算符,这样就可以将两个 `Point` 实例相加了:
|
|
|
|
|
|
|
|
|
|
<span class="filename">文件名:src/main.rs</span>
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
{{#rustdoc_include ../listings/ch20-advanced-features/listing-20-14/src/main.rs}}
|
|
|
|
|
{{#rustdoc_include ../listings/ch20-advanced-features/listing-20-15/src/main.rs}}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
<span class="caption">示例 19-14: 实现 `Add` trait 重载 `Point` 实例的 `+` 运算符</span>
|
|
|
|
|
<span class="caption">示例 20-14: 实现 `Add` trait 重载 `Point` 实例的 `+` 运算符</span>
|
|
|
|
|
|
|
|
|
|
`add` 方法将两个 `Point` 实例的 `x` 值和 `y` 值分别相加来创建一个新的 `Point`。`Add` trait 有一个叫做 `Output` 的关联类型,它用来决定 `add` 方法的返回值类型。
|
|
|
|
|
|
|
|
|
@ -77,7 +77,7 @@ trait Add<Rhs=Self> {
|
|
|
|
|
|
|
|
|
|
当为 `Point` 实现 `Add` 时,使用了默认的 `Rhs`,因为我们希望将两个 `Point` 实例相加。让我们看看一个实现 `Add` trait 时希望自定义 `Rhs` 类型而不是使用默认类型的例子。
|
|
|
|
|
|
|
|
|
|
这里有两个存放不同单元值的结构体,`Millimeters` 和 `Meters`。(这种将现有类型简单封装进另一个结构体的方式被称为 **newtype 模式**(*newtype pattern*,之后的 [“为了类型安全和抽象而使用 newtype 模式”][newtype] 部分会详细介绍。)我们希望能够将毫米值与米值相加,并让 `Add` 的实现正确处理转换。可以为 `Millimeters` 实现 `Add` 并以 `Meters` 作为 `Rhs`,如示例 19-15 所示。
|
|
|
|
|
这里有两个存放不同单元值的结构体,`Millimeters` 和 `Meters`。(这种将现有类型简单封装进另一个结构体的方式被称为 **newtype 模式**(*newtype pattern*,之后的 [“为了类型安全和抽象而使用 newtype 模式”][newtype] 部分会详细介绍。)我们希望能够将毫米值与米值相加,并让 `Add` 的实现正确处理转换。可以为 `Millimeters` 实现 `Add` 并以 `Meters` 作为 `Rhs`,如示例 20-15 所示。
|
|
|
|
|
|
|
|
|
|
<span class="filename">文件名:src/lib.rs</span>
|
|
|
|
|
|
|
|
|
@ -85,7 +85,7 @@ trait Add<Rhs=Self> {
|
|
|
|
|
{{#rustdoc_include ../listings/ch20-advanced-features/listing-20-15/src/lib.rs}}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
<span class="caption">示例 19-15: 在 `Millimeters` 上实现 `Add`,以便能够将 `Millimeters` 与 `Meters` 相加</span>
|
|
|
|
|
<span class="caption">示例 20-15: 在 `Millimeters` 上实现 `Add`,以便能够将 `Millimeters` 与 `Meters` 相加</span>
|
|
|
|
|
|
|
|
|
|
为了使 `Millimeters` 和 `Meters` 能够相加,我们指定 `impl Add<Meters>` 来设定 `Rhs` 类型参数的值而不是使用默认的 `Self`。
|
|
|
|
|
|
|
|
|
@ -102,7 +102,7 @@ trait Add<Rhs=Self> {
|
|
|
|
|
|
|
|
|
|
Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法,也不能阻止为同一类型同时实现这两个 trait。甚至直接在类型上实现开始已经有的同名方法也是可能的!
|
|
|
|
|
|
|
|
|
|
不过,当调用这些同名方法时,需要告诉 Rust 我们希望使用哪一个。考虑一下示例 19-16 中的代码,这里定义了 trait `Pilot` 和 `Wizard` 都拥有方法 `fly`。接着在一个本身已经实现了名为 `fly` 方法的类型 `Human` 上实现这两个 trait。每一个 `fly` 方法都进行了不同的操作:
|
|
|
|
|
不过,当调用这些同名方法时,需要告诉 Rust 我们希望使用哪一个。考虑一下示例 20-16 中的代码,这里定义了 trait `Pilot` 和 `Wizard` 都拥有方法 `fly`。接着在一个本身已经实现了名为 `fly` 方法的类型 `Human` 上实现这两个 trait。每一个 `fly` 方法都进行了不同的操作:
|
|
|
|
|
|
|
|
|
|
<span class="filename">文件名:src/main.rs</span>
|
|
|
|
|
|
|
|
|
@ -110,9 +110,9 @@ Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法
|
|
|
|
|
{{#rustdoc_include ../listings/ch20-advanced-features/listing-20-16/src/main.rs:here}}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
<span class="caption">示例 19-16: 两个 trait 定义为拥有 `fly` 方法,并在直接定义有 `fly` 方法的 `Human` 类型上实现这两个 trait</span>
|
|
|
|
|
<span class="caption">示例 20-16: 两个 trait 定义为拥有 `fly` 方法,并在直接定义有 `fly` 方法的 `Human` 类型上实现这两个 trait</span>
|
|
|
|
|
|
|
|
|
|
当调用 `Human` 实例的 `fly` 时,编译器默认调用直接实现在类型上的方法,如示例 19-17 所示。
|
|
|
|
|
当调用 `Human` 实例的 `fly` 时,编译器默认调用直接实现在类型上的方法,如示例 20-17 所示。
|
|
|
|
|
|
|
|
|
|
<span class="filename">文件名:src/main.rs</span>
|
|
|
|
|
|
|
|
|
@ -120,11 +120,11 @@ Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法
|
|
|
|
|
{{#rustdoc_include ../listings/ch20-advanced-features/listing-20-17/src/main.rs:here}}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
<span class="caption">示例 19-17: 调用 `Human` 实例的 `fly`</span>
|
|
|
|
|
<span class="caption">示例 20-17: 调用 `Human` 实例的 `fly`</span>
|
|
|
|
|
|
|
|
|
|
运行这段代码会打印出 `*waving arms furiously*`,这表明 Rust 调用了直接实现在 `Human` 上的 `fly` 方法。
|
|
|
|
|
|
|
|
|
|
为了能够调用 `Pilot` trait 或 `Wizard` trait 的 `fly` 方法,我们需要使用更明显的语法以便能指定我们指的是哪个 `fly` 方法。这个语法展示在示例 19-18 中:
|
|
|
|
|
为了能够调用 `Pilot` trait 或 `Wizard` trait 的 `fly` 方法,我们需要使用更明显的语法以便能指定我们指的是哪个 `fly` 方法。这个语法展示在示例 20-18 中:
|
|
|
|
|
|
|
|
|
|
<span class="filename">文件名:src/main.rs</span>
|
|
|
|
|
|
|
|
|
@ -132,9 +132,9 @@ Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法
|
|
|
|
|
{{#rustdoc_include ../listings/ch20-advanced-features/listing-20-18/src/main.rs:here}}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
<span class="caption">示例 19-18: 指定我们希望调用哪一个 trait 的 `fly` 方法</span>
|
|
|
|
|
<span class="caption">示例 20-18: 指定我们希望调用哪一个 trait 的 `fly` 方法</span>
|
|
|
|
|
|
|
|
|
|
在方法名前指定 trait 名向 Rust 澄清了我们希望调用哪个 `fly` 实现。也可以选择写成 `Human::fly(&person)`,这等同于示例 19-18 中的 `person.fly()`,不过如果无需消歧义的话这么写就有点长了。
|
|
|
|
|
在方法名前指定 trait 名向 Rust 澄清了我们希望调用哪个 `fly` 实现。也可以选择写成 `Human::fly(&person)`,这等同于示例 20-18 中的 `person.fly()`,不过如果无需消歧义的话这么写就有点长了。
|
|
|
|
|
|
|
|
|
|
运行这段代码会打印出:
|
|
|
|
|
|
|
|
|
@ -144,7 +144,7 @@ Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法
|
|
|
|
|
|
|
|
|
|
因为 `fly` 方法获取一个 `self` 参数,如果有两个 **类型** 都实现了同一 **trait**,Rust 可以根据 `self` 的类型计算出应该使用哪一个 trait 实现。
|
|
|
|
|
|
|
|
|
|
然而,并非所有关联函数都有 `self` 参数,比如非方法(non-method)函数。当存在多个类型或者 trait 定义了相同函数名的非方法函数时,Rust 就不总是能计算出我们期望的是哪一个类型,除非使用 **完全限定语法**(*fully qualified syntax*)。例如示例 19-19 中的创建了一个希望将所有小狗叫做 *Spot* 的动物收容所的 trait。`Animal` trait 有一个关联非方法函数 `baby_name`。结构体 `Dog` 实现了 `Animal`,同时又直接提供了关联非方法函数 `baby_name`。
|
|
|
|
|
然而,并非所有关联函数都有 `self` 参数,比如非方法(non-method)函数。当存在多个类型或者 trait 定义了相同函数名的非方法函数时,Rust 就不总是能计算出我们期望的是哪一个类型,除非使用 **完全限定语法**(*fully qualified syntax*)。例如示例 20-19 中的创建了一个希望将所有小狗叫做 *Spot* 的动物收容所的 trait。`Animal` trait 有一个关联非方法函数 `baby_name`。结构体 `Dog` 实现了 `Animal`,同时又直接提供了关联非方法函数 `baby_name`。
|
|
|
|
|
|
|
|
|
|
<span class="filename">文件名:src/main.rs</span>
|
|
|
|
|
|
|
|
|
@ -152,7 +152,7 @@ Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法
|
|
|
|
|
{{#rustdoc_include ../listings/ch20-advanced-features/listing-20-19/src/main.rs}}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
<span class="caption">示例 19-19: 一个带有关联函数的 trait 和一个带有同名关联函数并实现了此 trait 的类型</span>
|
|
|
|
|
<span class="caption">示例 20-19: 一个带有关联函数的 trait 和一个带有同名关联函数并实现了此 trait 的类型</span>
|
|
|
|
|
|
|
|
|
|
`Dog` 上定义的关联函数 `baby_name` 的实现代码将所有的小狗起名为 Spot。`Dog` 类型还实现了 `Animal` trait,它描述了所有动物的共有的特征。小狗被称为 puppy,这表现为 `Dog` 的 `Animal` trait 实现中与 `Animal` trait 相关联的函数 `baby_name`。
|
|
|
|
|
|
|
|
|
@ -162,7 +162,7 @@ Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法
|
|
|
|
|
{{#include ../listings/ch20-advanced-features/listing-20-19/output.txt}}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
这并不是我们需要的。我们希望调用的是 `Dog` 上 `Animal` trait 实现那部分的 `baby_name` 函数,这样能够打印出 `A baby dog is called a puppy`。示例 19-18 中用到的技术在这并不管用;如果将 `main` 改为示例 19-20 中的代码,则会得到一个编译错误:
|
|
|
|
|
这并不是我们需要的。我们希望调用的是 `Dog` 上 `Animal` trait 实现那部分的 `baby_name` 函数,这样能够打印出 `A baby dog is called a puppy`。示例 20-18 中用到的技术在这并不管用;如果将 `main` 改为示例 20-20 中的代码,则会得到一个编译错误:
|
|
|
|
|
|
|
|
|
|
<span class="filename">文件名:src/main.rs</span>
|
|
|
|
|
|
|
|
|
@ -170,7 +170,7 @@ Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法
|
|
|
|
|
{{#rustdoc_include ../listings/ch20-advanced-features/listing-20-20/src/main.rs:here}}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
<span class="caption">示例 19-20: 尝试调用 `Animal` trait 的 `baby_name` 函数,不过 Rust 并不知道该使用哪一个实现</span>
|
|
|
|
|
<span class="caption">示例 20-20: 尝试调用 `Animal` trait 的 `baby_name` 函数,不过 Rust 并不知道该使用哪一个实现</span>
|
|
|
|
|
|
|
|
|
|
因为 `Animal::baby_name` 没有 `self` 参数,同时这可能会有其它类型实现了 `Animal` trait,Rust 无法计算出所需的是哪一个 `Animal::baby_name` 实现。我们会得到这个编译错误:
|
|
|
|
|
|
|
|
|
@ -178,7 +178,7 @@ Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法
|
|
|
|
|
{{#include ../listings/ch20-advanced-features/listing-20-20/output.txt}}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
为了消歧义并告诉 Rust 我们希望使用的是 `Dog` 的 `Animal` 实现而不是其它类型的 `Animal` 实现,需要使用 **完全限定语法**,这是调用函数时最为明确的方式。示例 19-21 展示了如何使用完全限定语法:
|
|
|
|
|
为了消歧义并告诉 Rust 我们希望使用的是 `Dog` 的 `Animal` 实现而不是其它类型的 `Animal` 实现,需要使用 **完全限定语法**,这是调用函数时最为明确的方式。示例 20-21 展示了如何使用完全限定语法:
|
|
|
|
|
|
|
|
|
|
<span class="filename">文件名:src/main.rs</span>
|
|
|
|
|
|
|
|
|
@ -186,7 +186,7 @@ Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法
|
|
|
|
|
{{#rustdoc_include ../listings/ch20-advanced-features/listing-20-21/src/main.rs:here}}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
<span class="caption">示例 19-21: 使用完全限定语法来指定我们希望调用的是 `Dog` 上 `Animal` trait 实现中的 `baby_name` 函数</span>
|
|
|
|
|
<span class="caption">示例 20-21: 使用完全限定语法来指定我们希望调用的是 `Dog` 上 `Animal` trait 实现中的 `baby_name` 函数</span>
|
|
|
|
|
|
|
|
|
|
我们在尖括号中向 Rust 提供了类型注解,并通过在此函数调用中将 `Dog` 类型当作 `Animal` 对待,来指定希望调用的是 `Dog` 上 `Animal` trait 实现中的 `baby_name` 函数。现在这段代码会打印出我们期望的数据:
|
|
|
|
|
|
|
|
|
@ -216,7 +216,7 @@ Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法
|
|
|
|
|
**********
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
在 `outline_print` 的实现中,因为希望能够使用 `Display` trait 的功能,则需要说明 `OutlinePrint` 只能用于同时也实现了 `Display` 并提供了 `OutlinePrint` 需要的功能的类型。可以通过在 trait 定义中指定 `OutlinePrint: Display` 来做到这一点。这类似于为 trait 增加 trait bound。示例 19-22 展示了一个 `OutlinePrint` trait 的实现:
|
|
|
|
|
在 `outline_print` 的实现中,因为希望能够使用 `Display` trait 的功能,则需要说明 `OutlinePrint` 只能用于同时也实现了 `Display` 并提供了 `OutlinePrint` 需要的功能的类型。可以通过在 trait 定义中指定 `OutlinePrint: Display` 来做到这一点。这类似于为 trait 增加 trait bound。示例 20-22 展示了一个 `OutlinePrint` trait 的实现:
|
|
|
|
|
|
|
|
|
|
<span class="filename">文件名:src/main.rs</span>
|
|
|
|
|
|
|
|
|
@ -224,7 +224,7 @@ Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法
|
|
|
|
|
{{#rustdoc_include ../listings/ch20-advanced-features/listing-20-22/src/main.rs:here}}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
<span class="caption">示例 19-22: 实现 `OutlinePrint` trait,它要求来自 `Display` 的功能</span>
|
|
|
|
|
<span class="caption">示例 20-22: 实现 `OutlinePrint` trait,它要求来自 `Display` 的功能</span>
|
|
|
|
|
|
|
|
|
|
因为指定了 `OutlinePrint` 需要 `Display` trait,则可以在 `outline_print` 中使用 `to_string`,其会为任何实现 `Display` 的类型自动实现。如果不在 trait 名后增加 `: Display` 并尝试在 `outline_print` 中使用 `to_string`,则会得到一个错误说在当前作用域中没有找到用于 `&Self` 类型的方法 `to_string`。
|
|
|
|
|
|
|
|
|
@ -256,7 +256,7 @@ Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法
|
|
|
|
|
|
|
|
|
|
在第十章的 [“为类型实现 trait”][implementing-a-trait-on-a-type] 部分,我们提到了孤儿规则(orphan rule),它说明只有 trait 或类型对于当前 crate 是本地时,才可以在此类型上实现该 trait。一个绕开这个限制的方法是使用 **newtype 模式**(*newtype pattern*),它涉及到在一个元组结构体(第五章 [“用没有命名字段的元组结构体来创建不同的类型”][tuple-structs] 部分介绍了元组结构体)中创建一个新类型。这个元组结构体带有一个字段作为希望实现 trait 的类型的简单封装。接着这个封装类型对于 crate 是本地的,这样就可以在这个封装上实现 trait。*Newtype* 是一个源自 Haskell 编程语言的概念。使用这个模式没有运行时性能惩罚,这个封装类型在编译时就被省略了。
|
|
|
|
|
|
|
|
|
|
例如,如果想要在 `Vec<T>` 上实现 `Display`,而孤儿规则阻止我们直接这么做,因为 `Display` trait 和 `Vec<T>` 都定义于我们的 crate 之外。可以创建一个包含 `Vec<T>` 实例的 `Wrapper` 结构体,接着可以如列表 19-23 那样在 `Wrapper` 上实现 `Display` 并使用 `Vec<T>` 的值:
|
|
|
|
|
例如,如果想要在 `Vec<T>` 上实现 `Display`,而孤儿规则阻止我们直接这么做,因为 `Display` trait 和 `Vec<T>` 都定义于我们的 crate 之外。可以创建一个包含 `Vec<T>` 实例的 `Wrapper` 结构体,接着可以如列表 20-23 那样在 `Wrapper` 上实现 `Display` 并使用 `Vec<T>` 的值:
|
|
|
|
|
|
|
|
|
|
<span class="filename">文件名:src/main.rs</span>
|
|
|
|
|
|
|
|
|
@ -264,7 +264,7 @@ Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法
|
|
|
|
|
{{#rustdoc_include ../listings/ch20-advanced-features/listing-20-23/src/main.rs}}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
<span class="caption">示例 19-23: 创建 `Wrapper` 类型封装 `Vec<String>` 以便能够实现 `Display`</span>
|
|
|
|
|
<span class="caption">示例 20-23: 创建 `Wrapper` 类型封装 `Vec<String>` 以便能够实现 `Display`</span>
|
|
|
|
|
|
|
|
|
|
`Display` 的实现使用 `self.0` 来访问其内部的 `Vec<T>`,因为 `Wrapper` 是元组结构体而 `Vec<T>` 是结构体总位于索引 0 的项。接着就可以使用 `Wrapper` 中 `Display` 的功能了。
|
|
|
|
|
|