update ch19-04

This commit is contained in:
KaiserY 2017-07-31 17:59:23 +08:00
parent 76b4c0e954
commit a4fce32b58
2 changed files with 268 additions and 1 deletions

View File

@ -133,7 +133,7 @@ fn traverse(graph: &AGraph<Node=usize, Edge=(usize, usize)>) {}
Rust 并不允许创建自定义运算符或重载任意运算符,不过 `std::ops` 中所列出的运算符和相应的 trait 可以通过实现运算符相关 trait 来重载。例如,列表 19-25 中展示了如何在 `Point` 结构体上实现 `Add` trait 来重载 `+` 运算符,这样就可以将两个 `Point` 实例相加了:
<span class="filename">Filename: src/main.rs</span>
<span class="filename">文件名: src/main.rs</span>
```rust
use std::ops::Add;
@ -163,3 +163,264 @@ fn main() {
<span class="caption">列表 19-25实现 `Add` 来重载 `Point``+` 运算符</span>
这里实现了 `add` 方法将两个 `Point` 实例的 `x` 值和 `y` 值分别相加来创建一个新的 `Point`。`Add` trait 有一个叫做 `Output` 的关联类型,它用来决定 `add` 方法的返回值类型。
让我们更仔细的看看 `Add` trait。这里是其定义
```rust
trait Add<RHS=Self> {
type Output;
fn add(self, rhs: RHS) -> Self::Output;
}
```
这看来应该很熟悉;这是一个带有一个方法和一个关联类型的 trait。比较陌生的部分是尖括号中的 `RHS=Self`:这个语法叫做**默认类型参数***default type parameters*)。`RHS` 是一个泛型参数“right hand side” 的缩写),它用于 `add` 方法中的 `rhs` 参数。如果实现 `Add` trait 时不指定 `RHS` 的具体类型,`RHS` 的类型将是默认的 `Self` 类型(在其上实现 `Add` 的类型)。
让我们看看另一个实现了 `Add` trait 的例子。想象一下我们拥有两个存放不同的单元值的结构体,`Millimeters` 和 `Meters`。可以如列表 19-26 所示那样用不同的方式为 `Millimeters` 实现 `Add` trait
```rust
use std::ops::Add;
struct Millimeters(u32);
struct Meters(u32);
impl Add for Millimeters {
type Output = Millimeters;
fn add(self, other: Millimeters) -> Millimeters {
Millimeters(self.0 + other.0)
}
}
impl Add<Meters> for Millimeters {
type Output = Millimeters;
fn add(self, other: Meters) -> Millimeters {
Millimeters(self.0 + (other.0 * 1000))
}
}
```
<span class="caption">列表 19-26`Millimeters` 上实现 `Add`,以能够将`Millimeters` 与 `Millimeters` 相加和将 `Millimeters``Meters` 相加</span>
如果将 `Millimeters` 与其他 `Millimeters` 相加,则无需为 `Add` 参数化 `RHS` 类型,因为默认的 `Self` 正是我们希望的。如果希望实现 `Millimeters``Meters` 相加,那么需要声明为 `impl Add<Meters>` 来设定 `RHS` 类型参数的值。
默认参数类型主要用于如下两个方面:
1. 扩展类型而不破坏现有代码。
2. 允许以一种大部分用户都不需要的方法进行自定义。
`Add` trait 就是第二个目的一个例子:大部分时候你会将两个相似的类型相加。在 `Add` trait 定义中使用默认类型参数使得实现 trait 变得更容易,因为大部分时候无需指定这额外的参数。换句话说,这样就去掉了一些实现的样板代码。
第一个目的是相似的,但过程是反过来的:因为现有 trait 实现并没有指定类型参数,如果需要为现有 trait 增加类型参数,为其提供一个默认值将允许我们在不破坏现有实现代码的基础上扩展 trait 的功能。
### 完全限定语法与消歧义
Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法,也不能阻止为同一类型同时实现这两个 trait。甚至也可以直接在类型上实现相同名称的方法那么为了能使用相同的名称调用每一个方法需要告诉 Rust 我们希望使用哪个方法。考虑一下列表 19-27 中的代码trait `Foo``Bar` 都拥有方法 `f`,并在结构体 `Baz` 上实现了这两个 trait结构体也有一个叫做 `f` 的方法:
<span class="filename">文件名: src/main.rs</span>
```rust
trait Foo {
fn f(&self);
}
trait Bar {
fn f(&self);
}
struct Baz;
impl Foo for Baz {
fn f(&self) { println!("Bazs impl of Foo"); }
}
impl Bar for Baz {
fn f(&self) { println!("Bazs impl of Bar"); }
}
impl Baz {
fn f(&self) { println!("Baz's impl"); }
}
fn main() {
let b = Baz;
b.f();
}
```
<span class="caption">列表 19-27实现两个拥有相同名称的方法的 trait同时还有直接定义于结构体的同名方法</span>
对于 `Baz``Foo` trait 中方法 `f` 的实现,它打印出 `Baz's impl of Foo`。对于 `Baz``Bar` trait 中方法 `f` 的实现,它打印出 `Baz's impl of Bar`。直接定义于 `Baz``f` 实现打印出 `Baz's impl`。当调用 `b.f()` 时会发生什么呢在这个例子中Rust 总是会使用直接定义于 `Baz` 的实现并打印出 `Baz's impl`
为了能够调用 `Foo``Baz` 中的 `f` 方法而不是直接定义于 `Baz``f` 实现,则需要使用**完全限定语法***fully qualified syntax*)来调用方法。它像这样工作:对于任何类似如下的方法调用:
```rust
receiver.method(args);
```
可以像这样使用完全限定的方法调用:
```rust
<Type as Trait>::method(receiver, args);
```
所以为了消歧义并能够调用列表 19-27 中所有的 `f` 方法,需要在尖括号中指定每个希望 `Baz` 作为的 trait接着使用双冒号接着传递 `Baz` 实例作为第一个参数并调用 `f` 方法。列表 19-28 展示了如何调用 `Foo` 中的 `f`,和 `Bar` 中与 `b` 中的 `f`
<span class="filename">文件名: src/main.rs</span>
```rust
# trait Foo {
# fn f(&self);
# }
# trait Bar {
# fn f(&self);
# }
# struct Baz;
# impl Foo for Baz {
# fn f(&self) { println!("Bazs impl of Foo"); }
# }
# impl Bar for Baz {
# fn f(&self) { println!("Bazs impl of Bar"); }
# }
# impl Baz {
# fn f(&self) { println!("Baz's impl"); }
# }
#
fn main() {
let b = Baz;
b.f();
<Baz as Foo>::f(&b);
<Baz as Bar>::f(&b);
}
```
<span class="caption">列表 19-28使用完全限定语法调用作为`Foo` 和 `Bar` trait 一部分的 `f` 方法</span>
这会打印出:
```
Baz's impl
Bazs impl of Foo
Bazs impl of Bar
```
只在存在歧义时才需要 `Type as` 部分,只有需要 `Type as` 时才需要 `<>` 部分。所以如果在作用域中只有定义于 `Baz``Baz` 上实现的 `Foo` trait 的 `f` 方法的话,则可以使用 `Foo::f(&b)` 调用 `Foo` 中的 `f` 方法,因为无需与 `Bar` trait 相区别。
也可以使用 `Baz::f(&b)` 调用直接定义于 `Baz` 上的 `f` 方法,不过因为这个定义是在调用 `b.f()` 时默认使用的,并不要求调用此方法时使用完全限定的名称。
### 父 trait 用于在另一个 trait 中使用某 trait 的功能
有时我们希望当实现某 trait 时依赖另一个 trait 也被实现,如此这个 trait 就可以使用其他 trait 的功能。这个所需的 trait 是我们实现的 trait 的**父(超) trait***supertrait*)。
例如,加入我们希望创建一个带有 `outline_print` 方法的 trait `OutlinePrint`,它会打印出带有星号框的值。也就是说,如果 `Point` 实现了 `Display` 并返回 `(x, y)`,调用以 1 作为 `x` 和 3 作为 `y``Point` 实例的 `outline_print` 会显示如下:
```
**********
* *
* (1, 3) *
* *
**********
```
`outline_print` 的实现中,因为希望能够使用 `Display` trait 的功能,则需要说明 `OutlinePrint` 只能用于同时也实现了 `Display` 并提供了 `OutlinePrint` 需要的功能的类型。可以在 trait 定义中指定 `OutlinePrint: Display` 来做到这一点。这类似于为 trait 增加 trait bound。列表 19-29 展示了一个 `OutlinePrint` trait 的实现:
```rust
use std::fmt;
trait OutlinePrint: fmt::Display {
fn outline_print(&self) {
let output = self.to_string();
let len = output.len();
println!("{}", "*".repeat(len + 4));
println!("*{}*", " ".repeat(len + 2));
println!("* {} *", output);
println!("*{}*", " ".repeat(len + 2));
println!("{}", "*".repeat(len + 4));
}
}
```
<span class="caption">列表 19-29实现 `OutlinePrint` trait它要求来自 `Display` 的功能</span>
因为指定了 `OutlinePrint` 需要 `Display` trait则可以在 `outline_print` 中使用 `to_string``to_string` 会为任何实现 `Display` 的类型自动实现)。如果不在 trait 名后增加 `: Display` 并尝试在 `outline_print` 中使用 `to_string`,则会得到一个错误说在当前作用域中没有找到用于 `&Self` 类型的方法 `to_string`
如果尝试在一个没有实现 `Display` 的类型上实现 `OutlinePrint`,比如 `Point` 结构体:
```rust
# trait OutlinePrint {}
struct Point {
x: i32,
y: i32,
}
impl OutlinePrint for Point {}
```
则会得到一个错误说 `Display` 没有被实现而 `Display``OutlinePrint` 所需要:
```
error[E0277]: the trait bound `Point: std::fmt::Display` is not satisfied
--> src/main.rs:20:6
|
20 | impl OutlinePrint for Point {}
| ^^^^^^^^^^^^ the trait `std::fmt::Display` is not implemented for
`Point`
|
= note: `Point` cannot be formatted with the default formatter; try using
`:?` instead if you are using a format string
= note: required by `OutlinePrint`
```
一旦在 `Point` 上实现 `Display` 并满足 `OutlinePrint` 要求的限制,比如这样:
```rust
# struct Point {
# x: i32,
# y: i32,
# }
#
use std::fmt;
impl fmt::Display for Point {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
```
那么在 `Point` 实现 `OutlinePrint` trait 将能成功编译并可以在 `Point` 实例上调用 `outline_print` 来显示位于星号框中的点的值。
### newtype 模式用以在外部类型上实现外部 trait
在第十章中我们提到了孤儿规则orphan rule它说明只要 trait 或类型对于当前 crate 是本地的话就可以在此类型上实现该 trait。一个绕开这个限制的方法是使用**newtype 模式***newtype pattern*),它涉及到使用一个元组结构体来创建一个新类型,它带有一个字段作为希望实现 trait 的类型的简单封装。接着这个封装类型对于 crate 是本地的,这样就可以在这个封装上实现 trait。“Newtype” 是一个源自 Haskell 编程语言的概念。使用这个模式没有运行时性能惩罚。这个封装类型在编译时被省略了。
例如,如果想要在 `Vec` 上实现 `Display`,可以创建一个包含 `Vec` 实例的 `Wrapper` 结构体。接着可以如列表 19-30 那样在 `Wrapper` 上实现 `Display` 并使用 `Vec` 的值:
<span class="filename">文件名: src/main.rs</span>
```rust
use std::fmt;
struct Wrapper(Vec<String>);
impl fmt::Display for Wrapper {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "[{}]", self.0.join(", "))
}
}
fn main() {
let w = Wrapper(vec![String::from("hello"), String::from("world")]);
println!("w = {}", w);
}
```
<span class="caption">列表 19-30创建 `Wrapper` 类型封装 `Vec<String>` 以便实现 `Display`</span>
`Display` 的实现使用 `self.0` 来访问其内部的 `Vec`,接着就可以使用 `Wrapper``Display` 的功能了。
此方法的缺点是因为 `Wrapper` 是一个新类型,它没有定义于其值之上的方法;必须直接在 `Wrapper` 上实现 `Vec` 的所有方法,如 `push`、`pop` 等等,并代理到 `self.0` 上以便可以将 `Wrapper` 完全当作 `Vec` 处理。如果希望新类型拥有其内部类型的每一个方法,为封装类型实现第十五章讲到的 `Deref` trait 并返回其内部类型是一种解决方案。如果不希望封装类型拥有所有内部类型的方法,比如为了限制封装类型的行为,则必须自行实现所需的方法。
上面便是 newtype 模式如何与 trait 结合使用的;还有一个不涉及 trait 的实用模式。现在让我们将话题的焦点转移到一些与 Rust 类型系统交互的高级方法上来吧。

View File

@ -0,0 +1,6 @@
## 高级类型
> [ch19-04-advanced-types.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch19-04-advanced-types.md)
> <br>
> commit e084e1773667c8eae28d9aab6d4939348eec0092