@ -2,9 +2,9 @@
> [ch19-03-advanced-traits.md ](https://github.com/rust-lang/book/blob/master/src/ch19-03-advanced-traits.md )
> < br >
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
> commit 426f3e4ec17e539ae9905ba559411169d303a031
第十章 “trait: 定义共享的行为” 部分,我们第一次涉及到了 trait, 不过就像生命周期一样, 我们并没有覆盖一些较为高级的细节。现在我们更加了解 Rust 了,可以深入理解其本质了。
第十章 [ “trait: 定义共享的行为”][traits-defining-shared-behavior] 部分,我们第一次涉及到了 trait, 不过就像生命周期一样, 我们并没有覆盖一些较为高级的细节。现在我们更加了解 Rust 了,可以深入理解其本质了。
### 关联类型在 trait 定义中指定占位符类型
@ -12,7 +12,7 @@
本章所描述的大部分内容都非常少见。关联类型则比较适中;它们比本书其他的内容要少见,不过比本章中的很多内容要更常见。
一个带有关联类型的 trait 的例子是标准库提供的 `Iterator` trait。它有一个叫做 `Item` 的关联类型来替代遍历的值的类型。第十三章的 “`Iterator` trait 和 `next` 方法” 部分曾提到过 `Iterator` trait 的定义如示例 19-20 所示:
一个带有关联类型的 trait 的例子是标准库提供的 `Iterator` trait。它有一个叫做 `Item` 的关联类型来替代遍历的值的类型。第十三章的 [ “`Iterator` trait 和 `next` 方法”][the-iterator-trait-and-the-next-method] 部分曾提到过 `Iterator` trait 的定义如示例 19-1 2 所示:
```rust
pub trait Iterator {
@ -22,7 +22,7 @@ pub trait Iterator {
}
```
< span class = "caption" > 示例 19-20 : `Iterator` trait 的定义中带有关联类型 `Item` </ span >
< span class = "caption" > 示例 19-1 2: `Iterator` trait 的定义中带有关联类型 `Item` </ span >
`Item` 是一个占位类型,同时 `next` 方法定义表明它返回 `Option<Self::Item>` 类型的值。这个 trait 的实现者会指定 `Item` 的具体类型,然而不管实现者指定何种类型, `next` 方法都会返回一个包含了此具体类型值的 `Option` 。
@ -40,7 +40,7 @@ impl Iterator for Counter {
// --snip--
```
这类似于泛型。那么为什么 `Iterator` trait 不像示例 19-2 1 那样定义呢?
这类似于泛型。那么为什么 `Iterator` trait 不像示例 19-13 那样定义呢?
```rust
pub trait Iterator< T > {
@ -50,9 +50,9 @@ pub trait Iterator<T> {
< span class = "caption" > 示例 19-21: 一个使用泛型的 `Iterator` trait 假想定义</ span >
区别在于当如示例 19-2 1 那样使用泛型时,则不得不在每一个实现中标注类型。这是因为我们也可以实现为 `Iterator<String> for Counter` ,或任何其他类型,这样就可以有多个 `Counter` 的 `Iterator` 的实现。换句话说,当 trait 有泛型参数时,可以多次实现这个 trait, 每次需改变泛型参数的具体类型。接着当使用 `Counter` 的 `next` 方法时,必须提供类型注解来表明希望使用 `Iterator` 的哪一个实现。
区别在于当如示例 19-13 那样使用泛型时,则不得不在每一个实现中标注类型。这是因为我们也可以实现为 `Iterator<String> for Counter` ,或任何其他类型,这样就可以有多个 `Counter` 的 `Iterator` 的实现。换句话说,当 trait 有泛型参数时,可以多次实现这个 trait, 每次需改变泛型参数的具体类型。接着当使用 `Counter` 的 `next` 方法时,必须提供类型注解来表明希望使用 `Iterator` 的哪一个实现。
通过关联类型,则无需标注类型因为不能多次实现这个 trait。对于示例 19-20 使用关联类型的定义,我们只能选择一次 `Item` 会是什么类型,因为只能有一个 `impl Iterator for Counter` 。当调用 `Counter` 的 `next` 时不必每次指定我们需要 `u32` 值的迭代器。
通过关联类型,则无需标注类型因为不能多次实现这个 trait。对于示例 19-1 2 使用关联类型的定义,我们只能选择一次 `Item` 会是什么类型,因为只能有一个 `impl Iterator for Counter` 。当调用 `Counter` 的 `next` 时不必每次指定我们需要 `u32` 值的迭代器。
### 默认泛型类型参数和运算符重载
@ -60,7 +60,7 @@ pub trait Iterator<T> {
这种情况的一个非常好的例子是用于运算符重载。**运算符重载**( *Operator overloading*)是指在特定情况下自定义运算符(比如 `+` )行为的操作。
Rust 并不允许创建自定义运算符或重载任意运算符,不过 `std::ops` 中所列出的运算符和相应的 trait 可以通过实现运算符相关 trait 来重载。例如,示例 19-22 中展示了如何在 `Point` 结构体上实现 `Add` trait 来重载 `+` 运算符,这样就可以将两个 `Point` 实例相加了:
Rust 并不允许创建自定义运算符或重载任意运算符,不过 `std::ops` 中所列出的运算符和相应的 trait 可以通过实现运算符相关 trait 来重载。例如,示例 19-14 中展示了如何在 `Point` 结构体上实现 `Add` trait 来重载 `+` 运算符,这样就可以将两个 `Point` 实例相加了:
< span class = "filename" > 文件名: src/main.rs< / span >
@ -90,7 +90,7 @@ fn main() {
}
```
< span class = "caption" > 示例 19-22 : 实现 `Add` trait 重载 `Point` 实例的 `+` 运算符</ span >
< span class = "caption" > 示例 19-14 : 实现 `Add` trait 重载 `Point` 实例的 `+` 运算符</ span >
`add` 方法将两个 `Point` 实例的 `x` 值和 `y` 值分别相加来创建一个新的 `Point` 。`Add` trait 有一个叫做 `Output` 的关联类型,它用来决定 `add` 方法的返回值类型。
@ -108,7 +108,7 @@ trait Add<RHS=Self> {
当为 `Point` 实现 `Add` 时,使用了默认的 `RHS` ,因为我们希望将两个 `Point` 实例相加。让我们看看一个实现 `Add` trait 时希望自定义 `RHS` 类型而不是使用默认类型的例子
这里有两个存放不同单元值的结构体,`Millimeters` 和 `Meters` 。我们希望能够将毫米值与米值相加,并让 `Add` 的实现正确处理转换。可以为 `Millimeters` 实现 `Add` 并以 `Meters` 作为 `RHS` ,如示例 19-23 所示。
这里有两个存放不同单元值的结构体,`Millimeters` 和 `Meters` 。我们希望能够将毫米值与米值相加,并让 `Add` 的实现正确处理转换。可以为 `Millimeters` 实现 `Add` 并以 `Meters` 作为 `RHS` ,如示例 19-15 所示。
< span class = "filename" > 文件名: src/lib.rs< / span >
@ -127,7 +127,7 @@ impl Add<Meters> for Millimeters {
}
```
< span class = "caption" > 示例 19-23 : 在 `Millimeters` 上实现 `Add` ,以便能够将 `Millimeters` 与 `Meters` 相加</ span >
< span class = "caption" > 示例 19-15 : 在 `Millimeters` 上实现 `Add` ,以便能够将 `Millimeters` 与 `Meters` 相加</ span >
为了使 `Millimeters` 和 `Meters` 能够相加,我们指定 `impl Add<Meters>` 来设定 `RHS` 类型参数的值而不是使用默认的 `Self` 。
@ -144,7 +144,7 @@ impl Add<Meters> for Millimeters {
Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法,也不能阻止为同一类型同时实现这两个 trait。甚至直接在类型上实现开始已经有的同名方法也是可能的!
不过,当调用这些同名方法时,需要告诉 Rust 我们希望使用哪一个。考虑一下示例 19-24 中的代码,这里定义了 trait `Pilot` 和 `Wizard` 都拥有方法 `fly` 。接着在一个本身已经实现了名为 `fly` 方法的类型 `Human` 上实现这两个 trait。每一个 `fly` 方法都进行了不同的操作:
不过,当调用这些同名方法时,需要告诉 Rust 我们希望使用哪一个。考虑一下示例 19-16 中的代码,这里定义了 trait `Pilot` 和 `Wizard` 都拥有方法 `fly` 。接着在一个本身已经实现了名为 `fly` 方法的类型 `Human` 上实现这两个 trait。每一个 `fly` 方法都进行了不同的操作:
< span class = "filename" > 文件名: src/main.rs< / span >
@ -178,9 +178,9 @@ impl Human {
}
```
< span class = "caption" > 示例 19-24 : 两个 trait 定义为拥有 `fly` 方法,并在直接定义有 `fly` 方法的 `Human` 类型上实现这两个 trait</ span >
< span class = "caption" > 示例 19-16 : 两个 trait 定义为拥有 `fly` 方法,并在直接定义有 `fly` 方法的 `Human` 类型上实现这两个 trait</ span >
当调用 `Human` 实例的 `fly` 时,编译器默认调用直接是现在类型上的方法,如示例 19-25 所示:
当调用 `Human` 实例的 `fly` 时,编译器默认调用直接是现在类型上的方法,如示例 19-17 所示。
< span class = "filename" > 文件名: src/main.rs< / span >
@ -219,11 +219,11 @@ fn main() {
}
```
< span class = "caption" > 示例 19-25 : 调用 `Human` 实例的 `fly` </ span >
< span class = "caption" > 示例 19-17 : 调用 `Human` 实例的 `fly` </ span >
运行这段代码会打印出 `*waving arms furiously*` ,这表明 Rust 调用了直接实现在 `Human` 上的 `fly` 方法。
为了能够调用 `Pilot` trait 或 `Wizard` trait 的 `fly` 方法,我们需要使用更明显的语法以便能指定我们指的是哪个 `fly` 方法。这个语法展示在示例 19-26 中:
为了能够调用 `Pilot` trait 或 `Wizard` trait 的 `fly` 方法,我们需要使用更明显的语法以便能指定我们指的是哪个 `fly` 方法。这个语法展示在示例 19-18 中:
< span class = "filename" > 文件名: src/main.rs< / span >
@ -264,9 +264,9 @@ fn main() {
}
```
< span class = "caption" > 示例 19-26 : 指定我们希望调用哪一个 trait 的 `fly` 方法</ span >
< span class = "caption" > 示例 19-18 : 指定我们希望调用哪一个 trait 的 `fly` 方法</ span >
在方法名前指定 trait 名向 Rust 澄清了我们希望调用哪个 `fly` 实现。也可以选择写成 `Human::fly(&person)` ,这等同于示例 19-26 中的 `person.fly()` ,不过如果无需消歧义的话这么写就有点长了。
在方法名前指定 trait 名向 Rust 澄清了我们希望调用哪个 `fly` 实现。也可以选择写成 `Human::fly(&person)` ,这等同于示例 19-18 中的 `person.fly()` ,不过如果无需消歧义的话这么写就有点长了。
运行这段代码会打印出:
@ -278,7 +278,7 @@ Up!
因为 `fly` 方法获取一个 `self` 参数,如果有两个 ** 类型** 都实现了同一 **trait** , Rust 可以根据 `self` 的类型计算出应该使用哪一个 trait 实现。
然而,关联函数是 trait 的一部分,但没有 `self` 参数。当同一作用域的两个类型实现了同一 trait, Rust 就不能计算出我们期望的是哪一个类型,除非使用 ** 完全限定语法**( *fully qualified syntax*)。例如,拿示例 19-27 中的 `Animal` trait 来说,它有关联函数 `baby_name` ,结构体 `Dog` 实现了 `Animal` ,同时有关联函数 `baby_name` 直接定义于 `Dog` 之上:
然而,关联函数是 trait 的一部分,但没有 `self` 参数。当同一作用域的两个类型实现了同一 trait, Rust 就不能计算出我们期望的是哪一个类型,除非使用 ** 完全限定语法**( *fully qualified syntax*)。例如,拿示例 19-19 中的 `Animal` trait 来说,它有关联函数 `baby_name` ,结构体 `Dog` 实现了 `Animal` ,同时有关联函数 `baby_name` 直接定义于 `Dog` 之上:
< span class = "filename" > 文件名: src/main.rs< / span >
@ -306,7 +306,7 @@ fn main() {
}
```
< span class = "caption" > 示例 19-27 : 一个带有关联函数的 trait 和一个带有同名关联函数并实现了此 trait 的类型< / span >
< span class = "caption" > 示例 19-19 : 一个带有关联函数的 trait 和一个带有同名关联函数并实现了此 trait 的类型< / span >
这段代码用于一个动物收容所,他们将所有的小狗起名为 Spot, 这实现为定义于 `Dog` 之上的关联函数 `baby_name` 。`Dog` 类型还实现了 `Animal` trait, 它描述了所有动物的共有的特征。小狗被称为 puppy, 这表现为 `Dog` 的 `Animal` trait 实现中与 `Animal` trait 相关联的函数 `baby_name` 。
@ -316,7 +316,7 @@ fn main() {
A baby dog is called a Spot
```
这并不是我们需要的。我们希望调用的是 `Dog` 上 `Animal` trait 实现那部分的 `baby_name` 函数,这样能够打印出 `A baby dog is called a puppy` 。示例 19-26 中用到的技术在这并不管用;如果将 `main` 改为示例 19-28 中的代码,则会得到一个编译错误:
这并不是我们需要的。我们希望调用的是 `Dog` 上 `Animal` trait 实现那部分的 `baby_name` 函数,这样能够打印出 `A baby dog is called a puppy` 。示例 19-18 中用到的技术在这并不管用;如果将 `main` 改为示例 19-20 中的代码,则会得到一个编译错误:
< span class = "filename" > 文件名: src/main.rs< / span >
@ -326,7 +326,7 @@ fn main() {
}
```
< span class = "caption" > 示例 19-28 : 尝试调用 `Animal` trait 的 `baby_name` 函数,不过 Rust 并不知道该使用哪一个实现</ span >
< span class = "caption" > 示例 19-20 : 尝试调用 `Animal` trait 的 `baby_name` 函数,不过 Rust 并不知道该使用哪一个实现</ span >
因为 `Animal::baby_name` 是关联函数而不是方法,因此它没有 `self` 参数, Rust 无法计算出所需的是哪一个 `Animal::baby_name` 实现。我们会得到这个编译错误:
@ -340,7 +340,7 @@ error[E0283]: type annotations required: cannot resolve `_: Animal`
= note: required by `Animal::baby_name`
```
为了消歧义并告诉 Rust 我们希望使用的是 `Dog` 的 `Animal` 实现,需要使用 ** 完全限定语法**,这是调用函数时最为明确的方式。示例 19-29 展示了如何使用完全限定语法:
为了消歧义并告诉 Rust 我们希望使用的是 `Dog` 的 `Animal` 实现,需要使用 ** 完全限定语法**,这是调用函数时最为明确的方式。示例 19-21 展示了如何使用完全限定语法:
< span class = "filename" > 文件名: src/main.rs< / span >
@ -368,7 +368,7 @@ fn main() {
}
```
< span class = "caption" > 示例 19-29 : 使用完全限定语法来指定我们希望调用的是 `Dog` 上 `Animal` trait 实现中的 `baby_name` 函数</ span >
< span class = "caption" > 示例 19-21 : 使用完全限定语法来指定我们希望调用的是 `Dog` 上 `Animal` trait 实现中的 `baby_name` 函数</ span >
我们在尖括号中向 Rust 提供了类型注解,并通过在此函数调用中将 `Dog` 类型当作 `Animal` 对待,来指定希望调用的是 `Dog` 上 `Animal` trait 实现中的 `baby_name` 函数。现在这段代码会打印出我们期望的数据:
@ -398,7 +398,7 @@ A baby dog is called a puppy
**********
```
在 `outline_print` 的实现中,因为希望能够使用 `Display` trait 的功能,则需要说明 `OutlinePrint` 只能用于同时也实现了 `Display` 并提供了 `OutlinePrint` 需要的功能的类型。可以通过在 trait 定义中指定 `OutlinePrint: Display` 来做到这一点。这类似于为 trait 增加 trait bound。示例 19-30 展示了一个 `OutlinePrint` trait 的实现:
在 `outline_print` 的实现中,因为希望能够使用 `Display` trait 的功能,则需要说明 `OutlinePrint` 只能用于同时也实现了 `Display` 并提供了 `OutlinePrint` 需要的功能的类型。可以通过在 trait 定义中指定 `OutlinePrint: Display` 来做到这一点。这类似于为 trait 增加 trait bound。示例 19-22 展示了一个 `OutlinePrint` trait 的实现:
< span class = "filename" > 文件名: src/main.rs< / span >
@ -418,7 +418,7 @@ trait OutlinePrint: fmt::Display {
}
```
< span class = "caption" > 示例 19-30 : 实现 `OutlinePrint` trait, 它要求来自 `Display` 的功能</ span >
< span class = "caption" > 示例 19-22 : 实现 `OutlinePrint` trait, 它要求来自 `Display` 的功能</ span >
因为指定了 `OutlinePrint` 需要 `Display` trait, 则可以在 `outline_print` 中使用 `to_string` , 其会为任何实现 `Display` 的类型自动实现。如果不在 trait 名后增加 `: Display` 并尝试在 `outline_print` 中使用 `to_string` ,则会得到一个错误说在当前作用域中没有找到用于 `&Self` 类型的方法 `to_string` 。
@ -472,7 +472,7 @@ impl fmt::Display for Point {
### newtype 模式用以在外部类型上实现外部 trait
在第十章的 “为类型实现 trait” 部分, 我们提到了孤儿规则( orphan rule) , 它说明只要 trait 或类型对于当前 crate 是本地的话就可以在此类型上实现该 trait。一个绕开这个限制的方法是使用**newtype 模式**( *newtype pattern*),它涉及到在一个元组结构体(第五章 “用没有命名字段的元组结构体来创建不同的类型” 部分介绍了元组结构体)中创建一个新类型。这个元组结构体带有一个字段作为希望实现 trait 的类型的简单封装。接着这个封装类型对于 crate 是本地的,这样就可以在这个封装上实现 trait。“Newtype” 是一个源自( U.C.0079, 逃) Haskell 编程语言的概念。使用这个模式没有运行时性能惩罚,这个封装类型在编译时就被省略了。
在第十章的 [“为类型实现 trait”][implementing-a-trait-on-a-type] “为类型实现 trait” 部分, 我们提到了孤儿规则( orphan rule) , 它说明只要 trait 或类型对于当前 crate 是本地的话就可以在此类型上实现该 trait。一个绕开这个限制的方法是使用 **newtype 模式**( *newtype pattern*),它涉及到在一个元组结构体(第五章 [ “用没有命名字段的元组结构体来创建不同的类型”][tuple-structs] 部分介绍了元组结构体)中创建一个新类型。这个元组结构体带有一个字段作为希望实现 trait 的类型的简单封装。接着这个封装类型对于 crate 是本地的,这样就可以在这个封装上实现 trait。*Newtype* 是一个源自( U.C.0079, 逃) Haskell 编程语言的概念。使用这个模式没有运行时性能惩罚,这个封装类型在编译时就被省略了。
例如,如果想要在 `Vec<T>` 上实现 `Display` ,而孤儿规则阻止我们直接这么做,因为 `Display` trait 和 `Vec<T>` 都定义于我们的 crate 之外。可以创建一个包含 `Vec<T>` 实例的 `Wrapper` 结构体,接着可以如列表 19-31 那样在 `Wrapper` 上实现 `Display` 并使用 `Vec<T>` 的值:
@ -499,6 +499,15 @@ fn main() {
`Display` 的实现使用 `self.0` 来访问其内部的 `Vec<T>` ,因为 `Wrapper` 是元组结构体而 `Vec<T>` 是结构体总位于索引 0 的项。接着就可以使用 `Wrapper` 中 `Display` 的功能了。
此方法的缺点是,因为 `Wrapper` 是一个新类型,它没有定义于其值之上的方法;必须直接在 `Wrapper` 上实现 `Vec<T>` 的所有方法,这样就可以代理到`self.0` 上 —— 这就允许我们完全像 `Vec<T>` 那样对待 `Wrapper` 。如果希望新类型拥有其内部类型的每一个方法,为封装类型实现 `Deref` trait( 第十五章 “通过 `Deref` trait 将智能指针当作常规引用处理” 部分讨论过)并返回其内部类型是一种解决方案。如果不希望封装类型拥有所有内部类型的方法 —— 比如为了限制封装类型的行为 —— 则必须只自行实现所需的方法。
此方法的缺点是,因为 `Wrapper` 是一个新类型,它没有定义于其值之上的方法;必须直接在 `Wrapper` 上实现 `Vec<T>` 的所有方法,这样就可以代理到`self.0` 上 —— 这就允许我们完全像 `Vec<T>` 那样对待 `Wrapper` 。如果希望新类型拥有其内部类型的每一个方法,为封装类型实现 `Deref` trait( 第十五章 [ “通过 `Deref` trait 将智能指针当作常规引用处理”][smart-pointer-deref] 部分讨论过)并返回其内部类型是一种解决方案。如果不希望封装类型拥有所有内部类型的方法 —— 比如为了限制封装类型的行为 —— 则必须只自行实现所需的方法。
上面便是 newtype 模式如何与 trait 结合使用的;还有一个不涉及 trait 的实用模式。现在让我们将话题的焦点转移到一些与 Rust 类型系统交互的高级方法上来吧。
[implementing-a-trait-on-a-type]:
ch10-02-traits.html#implementing-a-trait-on-a-type
[the-iterator-trait-and-the-next-method]:
ch13-02-iterators.html#the-iterator-trait-and-the-next-method
[traits-defining-shared-behavior]:
ch10-02-traits.html#traits-defining-shared-behavior
[smart-pointer-deref]: ch15-02-deref.html#treating-smart-pointers-like-regular-references-with-the-deref-trait
[tuple-structs]: ch05-01-defining-structs.html#using-tuple-structs-without-named-fields-to-create-different-types