Merge branch 'master' into master

pull/55/head
Zheng Ping 8 years ago committed by GitHub
commit 6b8ee38a34

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -93,7 +93,13 @@
- [可扩展的并发:`Sync`和`Send`](ch16-04-extensible-concurrency-sync-and-send.md)
- [面向对象](ch17-00-oop.md)
- [什么是面向对象](ch17-01-what-is-oo.md)
- [trait对象](ch17-02-trait-objects.md)
- [什么是面向对象?](ch17-01-what-is-oo.md)
- [为使用不同类型的值而设计的 trait 对象](ch17-02-trait-objects.md)
- [面向对象设计模式的实现](ch17-03-oo-design-patterns.md)
## 高级主题
- [模式用来匹配值的结构](ch18-00-patterns.md)
- [所有可能会用到模式的位置](ch18-01-all-the-places-for-patterns.md)
- [refutable何时模式可能会匹配失败](ch18-02-refutability.md)
- [模式的全部语法](ch18-03-pattern-syntax.md)

@ -182,7 +182,7 @@ let hello = "Здравствуйте";
let answer = &hello[0];
```
`answer`的值应该是什么呢?它应该是第一个字符`З`吗?当使用 UTF-8 编码时,`З`的第一个字节是`208`,第二个是`151`,所以`answer`实际上应该是`208`,不过`208`自身并不是一个有效的字母。返回`208`可不是一个请求字符串第一个字母的人所希望看到的,不过它是 Rust 在字节索引零位置所能提供的唯一数据。返回字节值可能不是人们希望看到的,即便是只有拉丁字母时:`&"hello"[0]`会返回`104`而不是`h`。为了避免返回意想不到值并造成不能立刻发现的 bug。Rust 选择不编译这些代码并及早杜绝了误会的生。
`answer`的值应该是什么呢?它应该是第一个字符`З`吗?当使用 UTF-8 编码时,`З`的第一个字节是`208`,第二个是`151`,所以`answer`实际上应该是`208`,不过`208`自身并不是一个有效的字母。返回`208`可不是一个请求字符串第一个字母的人所希望看到的,不过它是 Rust 在字节索引零位置所能提供的唯一数据。返回字节值可能不是人们希望看到的,即便是只有拉丁字母时:`&"hello"[0]`会返回`104`而不是`h`。为了避免返回意想不到值并造成不能立刻发现的 bug。Rust 选择不编译这些代码并及早杜绝了误会的生。
#### 字节、标量值和字形簇!天呐!
@ -283,4 +283,4 @@ for b in "नमस्ते".bytes() {
总而言之字符串还是很复杂的。不同的语言选择了不同的向程序员展示其复杂性的方式。Rust 选择了以准确的方式处理`String`数据作为所有 Rust 程序的默认行为,这意味着程序员们必须更多的思考如何在前台处理 UTF-8 数据。这种权衡取舍相比其他语言更多的暴露出了字符串的复杂性,不过也使你在开发生命周期中免于处理涉及非 ASCII 字符的错误。
现在让我们转向一些不太复杂的集合:哈希 map
现在让我们转向一些不太复杂的集合:哈希 map

@ -10,7 +10,7 @@
the one with SpreadsheetCell. I will go back and add Listing 8-1 next time I
get Chapter 8 for editing. /Carol -->
有时我们希望使用的类型的集合对于使用库的程序员来说是可扩展的。例如很多图形用户接口GUI工具有一个条目列表的概念它通过遍历列表并对每一个条目调用`draw`方法来绘制在屏幕上。我们将要创建一个叫做`rust_gui`的包含一个 GUI 库结构的库 crate。GUI 库可以包含一些供开发者使用的类型,比如`Button`或`TextField`。使用`rust_gui`的程序员会想要创建更多可以绘制在屏幕上的类型:一个程序员可能会增加一个`Image`,而另一个可能会增加一个`SelectBox`。我们不会在本章节实现一个功能完善的 GUI 库,不过会展示各个部分是如何结合在一起的。
有时我们希望使用的类型的集合对于使用库的程序员来说是可扩展的。例如很多图形用户接口GUI工具有一个条目列表的概念它通过遍历列表并对每一个条目调用 `draw` 方法来绘制在屏幕上。我们将要创建一个叫做 `rust_gui` 的包含一个 GUI 库结构的库 crate。GUI 库可以包含一些供开发者使用的类型,比如 `Button` `TextField`。使用 `rust_gui` 的程序员会想要创建更多可以绘制在屏幕上的类型:一个程序员可能会增加一个 `Image`,而另一个可能会增加一个 `SelectBox`。我们不会在本章节实现一个功能完善的 GUI 库,不过会展示各个部分是如何结合在一起的。
当写 `rust_gui` 库时,我们不知道其他程序员需要什么类型,所以无法定义一个 `enum` 来包含所有的类型。然而 `rust_gui` 需要跟踪所有这些不同类型的值,需要有在每个值上调用 `draw` 方法能力。我们的 GUI 库不需要确切地知道调用 `draw` 方法会发生什么,只需要有可用的方法供我们调用。
@ -20,11 +20,11 @@ get Chapter 8 for editing. /Carol -->
不过在Rust语言中我们可以定义一个 `Draw` trait包含名为 `draw` 的方法。我们定义一个由*trait对象*组成的vector绑定了某种指针的trait比如`&`引用或者一个`Box<T>`智能指针。
之前提到,我们不会称结构体和枚举为对象,以区分其他语言的结构体和枚举对象。结构体或者枚举成员中的数据和`impl`块中的行为是分开的而其他语言则是数据和行为被组合到一个对象里。Trait 对象更像其他语言的对象因为他们将其指针指向的具体对象作为数据将在trait 中定义的方法作为行为组合在了一起。但是trait 对象和其他语言是不同的,我们不能向一个 trait 对象增加数据。trait 对象不像其他语言那样有用:它们的目的是允许从公有行为上抽象。
之前提到,我们不会称结构体和枚举为对象,以区分其他语言的结构体和枚举对象。结构体或者枚举成员中的数据和`impl`块中的行为是分开的而其他语言则是数据和行为被组合到一个对象里。Trait 对象更像其他语言的对象,因为他们将其指针指向的具体对象作为数据,将在 trait 中定义的方法作为行为组合在了一起。但是trait 对象和其他语言是不同的,我们不能向一个 trait 对象增加数据。trait 对象不像其他语言那样有用:它们的目的是允许从公有行为上抽象。
trait 对象定义了给定情况下应有的行为。当需要具有某种特性的不确定具体类型时,我们可以把 trait 对象当作 trait 使用。Rust 的类型系统会保证我们为 trait 对象带入的任何值会实现 trait 的方法。我们不需要在编译阶段知道所有可能的类型,却可以把所有的实例统一对待。Listing 17-03展示了如何定义一个名为`Draw`的带有`draw`方法的trait。
trait 对象定义了给定情况下应有的行为。当需要具有某种特性的不确定具体类型时,我们可以把 trait 对象当作 trait 使用。Rust 的类型系统会保证我们为 trait 对象带入的任何值会实现 trait 的方法。我们不需要在编译阶段知道所有可能的类型,却可以把所有的实例统一对待。列表 17-03 展示了如何定义一个名为`Draw`的带有`draw`方法的 trait。
<span class="filename">Filename: src/lib.rs</span>
<span class="filename">文件名: src/lib.rs</span>
```rust
pub trait Draw {
@ -32,13 +32,13 @@ pub trait Draw {
}
```
<span class="caption">Listing 17-3:`Draw` trait的定义</span>
<span class="caption">列表 17-3:`Draw` trait 的定义</span>
<!-- NEXT PARAGRAPH WRAPPED WEIRD INTENTIONALLY SEE #199 -->
因为我们已经在第10章讨论过如何定义 trait你可能比较熟悉。下面是新的定义Listing 17-4有一个名为 `Screen` 的结构体,里面有一个名为 `components` 的 vector`components` 的类型是Box<Draw>。`Box<Draw>` 是一个 trait 对象:它是 `Box` 内部任意一个实现了 `Draw` trait 的类型的替身。
因为我们已经在第十章讨论过如何定义 trait你可能比较熟悉。下面是新的定义列表 17-4 有一个名为 `Screen` 的结构体,里面有一个名为 `components` 的 vector`components` 的类型是 `Box<Draw>`。`Box<Draw>` 是一个 trait 对象:它是 `Box` 内部任意一个实现了 `Draw` trait 的类型的替身。
<span class="filename">Filename: src/lib.rs</span>
<span class="filename">文件名: src/lib.rs</span>
```rust
# pub trait Draw {
@ -50,13 +50,11 @@ pub struct Screen {
}
```
<span class="caption">Listing 17-4: 定义一个 `Screen` 结构体,带有一个含有实现了 `Draw` trait 的 `components` vector 成员
<span class="caption">列表 17-4: 一个 `Screen` 结构体的定义,它带有一个字段`components`,其包含实现了 `Draw` trait 的 trait 对象的 vector</span>
</span>
`Screen` 结构体上,我们将要定义一个 `run` 方法,该方法会在它的 `components` 上调用 `draw` 方法如Listing 17-5所示
`Screen` 结构体上,我们将要定义一个 `run` 方法,该方法会在它的 `components` 上的每一个元素调用 `draw` 方法,如列表 17-5 所示:
<span class="filename">Filename: src/lib.rs</span>
<span class="filename">文件名: src/lib.rs</span>
```rust
# pub trait Draw {
@ -76,12 +74,12 @@ impl Screen {
}
```
<span class="caption">Listing 17-5:在 `Screen` 上实现一个 `run` 方法,该方法在每个组件上调用 `draw` 方法
<span class="caption">列表 17-5:在 `Screen` 上实现一个 `run` 方法,该方法在每个 component 上调用 `draw` 方法
</span>
这与带 trait 约束的泛型结构体不同(trait 约束泛型参数)。泛型参数一次只能被一个具体类型替代,而 trait 对象可以在运行时允许多种具体类型填充 trait 对象。比如,我们已经定义了 `Screen` 结构体使用泛型和一个 trait 约束,如Listing 17-6所示:
这与带 trait 约束的泛型结构体不同(trait 约束泛型参数)。泛型参数一次只能被一个具体类型替代,而 trait 对象可以在运行时允许多种具体类型填充 trait 对象。比如,我们已经定义了 `Screen` 结构体使用泛型和一个 trait 约束,如列表 17-6 所示:
<span class="filename">Filename: src/lib.rs</span>
<span class="filename">文件名: src/lib.rs</span>
```rust
# pub trait Draw {
@ -102,7 +100,7 @@ impl<T> Screen<T>
}
```
<span class="caption">Listing 17-6: 一种 `Screen` 结构体的替代实现,它的 `run` 方法使用通用类型和 trait 绑定
<span class="caption">列表 17-6: 一种 `Screen` 结构体的替代实现,它的 `run` 方法使用通用类型和 trait 绑定
</span>
这个例子中,`Screen` 实例所有组件类型必需全是 `Button`,或者全是 `TextField`。如果你的组件集合是单一类型的,那么可以优先使用泛型和 trait 约束,因为其使用的具体类型在编译阶段即可确定。
@ -111,9 +109,9 @@ impl<T> Screen<T>
### 来自我们或者库使用者的实现
现在,我们增加一些实现了 `Draw` trait 的类型,再次提供 `Button`。实现一个 GUI 库实际上超出了本书的范围,因此 `draw` 方法留空。为了想象实现可能的样子,`Button` 结构体有 `width`、`height` 和 `label`字段,如Listing 17-7所示:
现在,我们增加一些实现了 `Draw` trait 的类型,再次提供 `Button`。实现一个 GUI 库实际上超出了本书的范围,因此 `draw` 方法留空。为了想象实现可能的样子,`Button` 结构体有 `width`、`height` 和 `label`字段,如列表 17-7 所示:
<span class="filename">Filename: src/lib.rs</span>
<span class="filename">文件名: src/lib.rs</span>
```rust
# pub trait Draw {
@ -133,14 +131,14 @@ impl Draw for Button {
}
```
<span class="caption">Listing 17-7: 实现了`Draw` trait的`Button` 结构体</span>
<span class="caption">列表 17-7: 实一个现了`Draw` trait 的 `Button` 结构体</span>
`Button` 上的 `width`、`height` 和 `label` 会和其他组件不同,比如 `TextField` 可能有 `width`、`height`,
`label` 以及 `placeholder` 字段。每个我们可以在屏幕上绘制的类型都会实现 `Draw` trait`draw` 方法中使用不同的代码,定义了如何绘制 `Button`。除了 `Draw` trait`Button` 也可能有一个 `impl` 块,包含按钮被点击时的响应方法。这类方法不适用于 `TextField` 这样的类型。
假定我们的库的用户相要实现一个包含 `width`、`height` 和 `options``SelectBox` 结构体。同时也在 `SelectBox` 类型上实现了 `Draw` traitListing 17-8所示:
假定我们的库的用户相要实现一个包含 `width`、`height` 和 `options``SelectBox` 结构体。同时也在 `SelectBox` 类型上实现了 `Draw` trait列表 17-8 所示:
<span class="filename">Filename: src/main.rs</span>
<span class="filename">文件名: src/main.rs</span>
```rust
extern crate rust_gui;
@ -159,12 +157,12 @@ impl Draw for SelectBox {
}
```
<span class="caption">Listing 17-8: 另外一个 crate 中,在 `SelectBox` 结构体上使用 `rust_gui` 和实现了`Draw` trait
<span class="caption">列表 17-8: 另外一个 crate 中,在 `SelectBox` 结构体上使用 `rust_gui` 和实现了`Draw` trait
</span>
库的用户现在可以在他们的 `main` 函数中创建一个 `Screen` 实例,然后把自身放入 `Box<T>` 变成 trait 对象,向 screen 增加 `SelectBox``Button`。他们可以在这个 `Screen` 实例上调用 `run` 方法,这又会调用每个组件的 `draw` 方法。 Listing 17-9 展示了实现:
库的用户现在可以在他们的 `main` 函数中创建一个 `Screen` 实例,然后把自身放入 `Box<T>` 变成 trait 对象,向 screen 增加 `SelectBox``Button`。他们可以在这个 `Screen` 实例上调用 `run` 方法,这又会调用每个组件的 `draw` 方法。 列表 17-9 展示了实现:
<span class="filename">Filename: src/main.rs</span>
<span class="filename">文件名: src/main.rs</span>
```rust
use rust_gui::{Screen, Button};
@ -193,7 +191,7 @@ fn main() {
}
```
<span class="caption">Listing 17-9: 使用 trait 对象来存储实现了相同 trait 的不同类型
<span class="caption">列表 17-9: 使用 trait 对象来存储实现了相同 trait 的不同类型
</span>
虽然我们不知道哪一天会有人增加 `SelectBox` 类型,但是我们的 `Screen` 能够操作 `SelectBox` 并绘制它,因为 `SelectBox` 实现了 `Draw` 类型,这意味着它实现了 `draw` 方法。
@ -202,9 +200,9 @@ fn main() {
Rust 类型系统使用 trait 对象来支持 duck typing 的好处是,我们无需在运行时检查一个值是否实现了特定方法,或是担心调用了一个值没有实现的方法。如果值没有实现 trait 对象需要的 trait方法Rust 不会编译。
比如,Listing 17-10 展示了当我们创建一个使用 `String` 做为其组件的 `Screen` 时发生的情况:
比如,列表 17-10 展示了当我们创建一个使用 `String` 做为其组件的 `Screen` 时发生的情况:
<span class="filename">Filename: src/main.rs</span>
<span class="filename">文件名: src/main.rs</span>
```rust
extern crate rust_gui;
@ -221,13 +219,13 @@ fn main() {
}
```
<span class="caption">Listing 17-10: 尝试使用一种没有实现 trait 对象的类型
<span class="caption">列表 17-10: 尝试使用一种没有实现 trait 对象的类型
</span>
我们会遇到这个错误,因为 `String` 没有实现 `Draw` trait
```text
```
error[E0277]: the trait bound `std::string::String: Draw` is not satisfied
-->
|
@ -242,9 +240,9 @@ error[E0277]: the trait bound `std::string::String: Draw` is not satisfied
### Trait 对象执行动态分发
回忆一下第10章我们讨论过的,当我们在泛型上使用 trait 约束时,编译器按单态类型处理:在需要使用范型参数的地方,编译器为每个具体类型生成非泛型的函数和方法实现。单态类型处理产生的代码实际就是做 *static dispatch*:方法的代码在编译阶段就已经决定了,当调用时,寻找那段代码非常快速。
回忆一下第章我们讨论过的,当我们在泛型上使用 trait 约束时,编译器按单态类型处理:在需要使用范型参数的地方,编译器为每个具体类型生成非泛型的函数和方法实现。单态类型处理产生的代码实际就是做 *static dispatch*:方法的代码在编译阶段就已经决定了,当调用时,寻找那段代码非常快速。
当我们使用 trait 对象,编译器不能按单态类型处理,因为无法知道使用代码的所有可能类型。而是调用方法的时候Rust 跟踪可能被使用的代码,在运行时找出调用该方法时应使用的代码。这也是我们熟知的 *dynamic dispatch*,查找过程会产生运行时开销。动态分发也会阻止编译器内联函数,失去一些优化途径。尽管获得了额外的灵活性,但仍然需要权衡取舍。
当我们使用 trait 对象编译器不能按单态类型处理因为无法知道使用代码的所有可能类型。而是调用方法的时候Rust 跟踪可能被使用的代码,在运行时找出调用该方法时应使用的代码。这也是我们熟知的 *dynamic dispatch*,查找过程会产生运行时开销。动态分发也会阻止编译器内联函数,失去一些优化途径。尽管获得了额外的灵活性,但仍然需要权衡取舍。
### Trait 对象需要对象安全
@ -258,16 +256,16 @@ objects. Clone is an example of one. You'll get errors that will let you know
if a trait can't be a trait object, look up object safety if you're interested
in the details"? Thanks! /Carol -->
不是所有的trait都可以被放进trait对象中; 只有*对象安全的*trait才可以这样做. 一个trait只有同时满足如下两点时才被认为是对象安全的:
不是所有的 trait 都可以被放进 trait 对象中; 只有*对象安全的**object safe*trait 才可以这样做. 一个 trait 只有同时满足如下两点时才被认为是对象安全的:
* 该trait要求`Self`不是`Sized`;
* 该trait的所有方法都是对象安全的;
* 该 trait 要求 `Self` 不是 `Sized`;
* 该 trait 的所有方法都是对象安全的;
`Self`是一个类型的别名关键字它表示当前正被实现的trait类型或者是方法所属的类型. `Sized`是一个像在第16章中介绍的`Send`和`Sync`那样的标记trait, 在编译时它会自动被放进大小确定的类型里,比如`i32`和引用. 大小不确定的类型有切片(`[T]`)和trait对象.
`Self` 是一个类型的别名关键字,它表示当前正被实现的 trait 类型或者是方法所属的类型. `Sized`是一个像在第十六章中介绍的`Send`和`Sync`那样的标记 trait, 在编译时它会自动被放进大小确定的类型里,比如`i32`和引用. 大小不确定的类型有 slice`[T]`)和 trait 对象.
`Sized`是一个默认会被绑定到所有常规类型参数的内隐trait. Rust中要求一个类型是`Sized`的最具可用性的用法是让`Sized`成为一个默认的trait绑定这样我们就可以在大多数的常规的用法中不去写`T: Sized`了. 如果我们想在切片(slice)中使用一个trait, 我们需要取消对`Sized`的trait绑定, 我们只需制定`T: ?Sized`作为trait绑定.
`Sized` 是一个默认会被绑定到所有常规类型参数的内隐 trait. Rust 中要求一个类型是`Sized`的最具可用性的用法是让`Sized`成为一个默认的 trait 绑定,这样我们就可以在大多数的常规的用法中不去写 `T: Sized` 了. 如果我们想在切片slice中使用一个 trait, 我们需要取消对`Sized`的 trait 绑定, 我们只需制定`T: ?Sized`作为 trait 绑定.
默认绑定到`Self: ?Sized`的trait可以被实现到是`Sized`或非`Sized`的类型上. 如果我们创建一个不绑定`Self: ?Sized`的trait`Foo`,它看上去应该像这样:
默认绑定到 `Self: ?Sized` trait 可以被实现到是 `Sized` 或非 `Sized` 的类型上. 如果我们创建一个不绑定 `Self: ?Sized` trait `Foo`,它看上去应该像这样:
```rust
trait Foo: Sized {
@ -275,21 +273,21 @@ trait Foo: Sized {
}
```
Trait`Sized`现在就是trait`Foo`的一个*超级trait*, 也就是说trait`Foo`需要实现了`Foo`的类型(即`Self`)是`Sized`. 我们将在第19章中更详细的介绍超trait(supertrait).
Trait `Sized`现在就是 trait `Foo`的一个*超级 trait**supertrait*, 也就是说 trait `Foo` 需要实现了 `Foo` 的类型(即`Self`)是`Sized`. 我们将在第十九章中更详细的介绍超 traitsupertrait.
像`Foo`那样要求`Self`是`Sized`的trait不允许成为trait对象的原因是不可能为trait对象`Foo`实现trait`Foo`: trait对象是无确定大小的但是`Foo`要求`Self`是`Sized`. 一个类型不可能同时既是有大小的又是无确定大小的.
像`Foo`那样要求`Self`是`Sized`的 trait 不允许成为 trait 对象的原因是不可能为 trait 对象`Foo`实现 trait `Foo`: trait 对象是无确定大小的,但是 `Foo` 要求 `Self` `Sized`. 一个类型不可能同时既是有大小的又是无确定大小的.
第二点说对象安全要求一个trait的所有方法必须是对象安全的. 一个对象安全的方法满足下列条件:
第二点说对象安全要求一个 trait 的所有方法必须是对象安全的. 一个对象安全的方法满足下列条件:
* 它要求`Self`是`Sized`或者
* 它要求 `Self` `Sized` 或者
* 它符合下面全部三点:
* 它不包含任意类型的常规参数
* 它的第一个参数必须是类型`Self`或一个引用到`Self`的类型(也就是说它必须是一个方法而非关联函数并且以`self`、`&self`或`&mut self`作为第一个参数)
* 除了第一个参数外它不能在其它地方用`Self`作为方法的参数签名
* 它的第一个参数必须是类型 `Self` 或一个引用到 `Self` 的类型(也就是说它必须是一个方法而非关联函数并且以 `self`、`&self` `&mut self` 作为第一个参数)
* 除了第一个参数外它不能在其它地方用 `Self` 作为方法的参数签名
虽然这些规则有一点形式化, 但是换个角度想一下: 如果你的方法在它的参数签名的其它地方也需要具体的`Self`类型参数, 但是一个对象又忘记了它的具体类型是什么, 这时该方法就无法使用被它忘记的原先的具体类型. 当该trait被使用时, 被具体类型参数填充的常规类型参数也是如此: 这个具体的类型就成了实现该trait的类型的某一部分, 如果使用一个trait对象时这个类型被抹掉了, 就没有办法知道该用什么类型来填充这个常规类型参数.
虽然这些规则有一点形式化, 但是换个角度想一下: 如果你的方法在它的参数签名的其它地方也需要具体的 `Self` 类型参数, 但是一个对象又忘记了它的具体类型是什么, 这时该方法就无法使用被它忘记的原先的具体类型. 当该 trait 被使用时, 被具体类型参数填充的常规类型参数也是如此: 这个具体的类型就成了实现该 trait 的类型的某一部分, 如果使用一个 trait 对象时这个类型被抹掉了, 就没有办法知道该用什么类型来填充这个常规类型参数.
一个trait的方法不是对象安全的一个例子是标准库中的`Clone`trait. `Clone`trait的`clone`方法的参数签名是这样的:
一个 trait 的方法不是对象安全的一个例子是标准库中的 `Clone` trait. `Clone` trait `clone` 方法的参数签名是这样的:
```rust
pub trait Clone {
@ -297,13 +295,13 @@ pub trait Clone {
}
```
`String`实现了`Clone` trait, 当我们在一个`String实例上调用`clone`方法时, 我们会得到一个`String`实例. 同样地, 如果我们在一个`Vec`实例上调用`clone`方法, 我们会得到一个`Vec`实例. `clone`的参数签名需要知道`Self`是什么类型, 因为它需要返回这个类型.
`String` 实现了 `Clone` trait, 当我们在一个 `String` 实例上调用 `clone` 方法时, 我们会得到一个 `String` 实例. 同样地, 如果我们在一个 `Vec` 实例上调用 `clone` 方法, 我们会得到一个 `Vec` 实例. `clone` 的参数签名需要知道 `Self` 是什么类型, 因为它需要返回这个类型.
如果我们想在像17-3中列出的`Draw`trait那样的trait上实现`Clone`, 我们就不知道`Self`将会是一个`Button`, 一个`SelectBox`, 或者是其它的在将来要实现`Draw`trait的类型.
如果我们像列表 17-3 中列出的 `Draw` trait 那样的 trait 上实现 `Clone`, 我们就不知道 `Self` 将会是一个 `Button`, 一个 `SelectBox`, 或者是其它的在将来要实现 `Draw` trait 的类型.
如果你做了违反trait对象的对象安全性规则的事情, 编译器将会告诉你. 比如, 如果你实现在17-4中列出的`Screen`结构, 你想让该结构像这样持有实现了`Clone`trait的类型而不是`Draw`trait:
如果你做了违反 trait 对象的对象安全性规则的事情, 编译器将会告诉你. 比如, 如果你实现在列表 17-4 中列出的 `Screen` 结构, 你想让该结构像这样持有实现了 `Clone` trait 的类型而不是 `Draw` trait:
```rust,ignore
```rust
pub struct Screen {
pub components: Vec<Box<Clone>>,
}

@ -451,4 +451,4 @@ fn main() {
阅读本章后,不管你是否认为 Rust 是一个面向对象语言,现在你都见识了 trait 对象是一个 Rust 中获取部分面向对象功能的方法。动态分发可以通过牺牲一些运行时性能来为你的代码提供一些灵活性。这些灵活性可以用来实现有助于代码可维护性的面向对象模式。Rust 也有像所有权这样不同于面向对象语言的功能。面向对象模式并不总是利用 Rust 实力的最好方式。
接下来,让我们看看另一个提供了很多灵活性的 Rust 功能:模式。贯穿本书我们都曾简单的见过他们,但并没有见识过他们的全部本领。让我们开始吧!
接下来,让我们看看另一个提供了多样灵活性的Rust功能模式。贯穿全书的模式, 我们已经和它们打过照面了,但并没有见识过它们的全部本领。让我们开始探索吧!

@ -0,0 +1,11 @@
# 模式用来匹配值的结构
> [ch18-00-patterns.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch18-00-patterns.md)
> <br>
> commit 3d47ebddad51b0080a19857e1495675a8e9376ef
模式是 Rust 中特殊的语法,它用来匹配类型中的结构,无论类型是简单还是复杂。模式由一些常量组成;解构数组、枚举、结构体或者是元组;变量、通配符和占位符。这些部分描述了我们要处理的数据的“形状”。
我们通过将一些值与模式相比较来使用它。如果模式匹配这些值,我们对值部分进行相应处理。回忆一下第六章讨论 `match` 表达式时像硬币分类器那样使用模式。我们可以为形状中的片段命名,就像在第六章中命名出现在二十五美分硬币上的州那样,如果数据符合这个形状,就可以使用这些命名的片段。
本章是所有模式相关内容的参考。我们将涉及到使用模式的有效位置,*refutable* 与 *irrefutable* 模式的区别,和你可能会见到的不同类型的模式语法。

@ -0,0 +1,172 @@
## 所有可能会用到模式的位置
> [ch18-01-all-the-places-for-patterns.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch18-01-all-the-places-for-patterns.md)
> <br>
> commit 4ca9e513e532a4d229ab5af7dfcc567129623bf4
模式出现在 Rust 的很多地方。你已经在不经意间使用了很多模式!本部分是一个所有有效模式位置的参考。
### `match` 分支
如第六章所讨论的,一个模式常用的位置是 `match` 表达式的分支。在形式上 `match` 表达式由 `match` 关键字、用于匹配的值和一个或多个分支构成。这些分支包含一个模式和在值匹配分支的模式时运行的表达式:
```
match VALUE {
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
}
```
#### 穷尽性和默认模式 `_`
`match` 表达式必须是穷尽的。当我们把所有分支的模式都放在一起,`match` 表达式所有可能的值都应该被考虑到。一个确保覆盖每个可能值的方法是在最后一个分支使用捕获所有的模式,比如一个变量名。一个匹配任何值的名称永远也不会失败,因此可以覆盖之前分支模式匹配剩下的情况。
这有一个额外的模式经常被用于结尾的分支:`_`。它匹配所有情况,不过它从不绑定任何变量。这在例如只希望在某些模式下运行代码而忽略其他值的时候很有用。
### `if let` 表达式
第六章讨论过了 `if let` 表达式,以及它是如何成为编写等同于只关心一个情况的 `match` 语句的简写的。`if let` 可以对应一个可选的 `else` 和代码在 `if let` 中的模式不匹配时运行。
列表 18-1 展示了甚至可以组合并匹配 `if let`、`else if` 和 `else if let`。这些代码展示了一系列针对不同条件的检查来决定背景颜色应该是什么。为了达到这个例子的目的,我们创建了硬编码值的变量,在真实程序中则可能由询问用户获得。如果用户指定了中意的颜色,我们将使用它作为背景颜色。如果今天是星期二,背景颜色将是绿色。如果用户指定了他们的年龄字符串并能够成功将其解析为数字的话,我们将根据这个数字使用紫色或者橙色。最后,如果没有一个条件符合,背景颜色将是蓝色:
<span class="filename">文件名: src/main.rs</span>
```rust
fn main() {
let favorite_color: Option<&str> = None;
let is_tuesday = false;
let age: Result<u8, _> = "34".parse();
if let Some(color) = favorite_color {
println!("Using your favorite color, {}, as the background", color);
} else if is_tuesday {
println!("Tuesday is green day!");
} else if let Ok(age) = age {
if age > 30 {
println!("Using purple as the background color");
} else {
println!("Using orange as the background color");
}
} else {
println!("Using blue as the background color");
}
}
```
<span class="caption">列表 18-1: 结合 `if let`、`else if`、`else if let` 和 `else`</span>
这个条件结构允许我们支持复杂的需求。使用这里硬编码的值,例子会打印出 `Using purple as the background color`
注意 `if let` 也可以像 `match` 分支那样引入覆盖变量:`if let Ok(age) = age` 引入了一个新的覆盖变量 `age`,它包含 `Ok` 成员中的值。这也意味着 `if age > 30` 条件需要位于这个代码块内部;不能将两个条件组合为 `if let Ok(age) = age && age > 30`,因为我们希望与 30 进行比较的被覆盖的 `age` 直到大括号开始的新作用域才是有效的。
另外注意这样有很多情况的条件并没有 `match` 表达式强大,因为其穷尽性没有为编译器所检查。如果去掉最后的 `else` 块而遗漏处理一些情况,编译器也不会报错。这个例子可能过于复杂以致难以重写为一个可读的 `match`,所以需要额外注意处理了所有的情况,因为编译器不会为我们检查穷尽性。
### `while let`
一个与 `if let` 类似的结构体是 `while let`:它允许只要模式匹配就一直进行 `while` 循环。列表 18-2 展示了一个使用 `while let` 的例子,它使用 vector 作为栈并打以先进后出的方式打印出 vector 中的值:
```rust
let mut stack = Vec::new();
stack.push(1);
stack.push(2);
stack.push(3);
while let Some(top) = stack.pop() {
println!("{}", top);
}
```
<span class="caption">列表 18-2: 使用 `while let` 循环只要 `stack.pop()` 返回 `Some`就打印出其值</span>
这个例子会打印出 3、2 和 1。`pop` 方法取出 vector 的最后一个元素并返回`Some(value)`,如果 vector 是空的,它返回 `None`。`while` 循环只要 `pop` 返回 `Some` 就会一直运行其块中的代码。一旦其返回 `None``while`循环停止。我们可以使用 `while let` 来弹出栈中的每一个元素。
### `for` 循环
`for` 循环,如同第三章所讲的,是 Rust 中最常见的循环结构。那一章所没有讲到的是 `for` 可以获取一个模式。列表 18-3 中展示了如何使用 `for` 循环来解构一个元组。`enumerate` 方法适配一个迭代器来产生元组,其包含值和值的索引:
```rust
let v = vec![1, 2, 3];
for (index, value) in v.iter().enumerate() {
println!("{} is at index {}", value, index);
}
```
<span class="caption">列表 18-3: 在 `for` 循环中使用模式来解构 `enumerate` 返回的元组</span>
这会打印出:
```
1 is at index 0
2 is at index 1
3 is at index 2
```
第一个 `enumerate` 调用会产生元组 `(0, 1)`。当这个匹配模式 `(index, value)``index` 将会是 0 而 `value` 将会是 1。
### `let` 语句
`match``if let` 都是本书之前明确讨论过的使用模式的位置,不过他们不是仅有的**使用过**模式的地方。例如,考虑一下这个直白的 `let` 变量赋值:
```rust
let x = 5;
```
本书进行了不下百次这样的操作。你可能没有发觉,不过你这正是在使用模式!`let` 语句更为正式的样子如下:
```
let PATTERN = EXPRESSION;
```
我们见过的像 `let x = 5;` 这样的语句中变量名位于 `PATTERN` 位置;变量名不过是形式特别朴素的模式。
通过 `let`,我们将表达式与模式比较,并为任何找到的名称赋值。所以例如 `let x = 5;` 的情况,`x` 是一个模式代表“将匹配到的值绑定到变量 x”。同时因为名称 `x` 是整个模式,这个模式实际上等于“将任何值绑定到变量 `x`,不过它是什么”。
为了更清楚的理解 `let` 的模式匹配的方面,考虑列表 18-4 中使用 `let` 和模式解构一个元组:
```rust
let (x, y, z) = (1, 2, 3);
```
<span class="caption">列表 18-4: 使用模式解构元组并一次创建三个变量</span>
这里有一个元组与模式匹配。Rust 会比较值 `(1, 2, 3)` 与模式 `(x, y, z)` 并发现值匹配这个模式。在这个例子中,将会把 `1` 绑定到 `x``2` 绑定到 `y` `3` 绑定到 `z`。你可以将这个元组模式看作是将三个独立的变量模式结合在一起。
在第十六章中我们见过另一个解构元组的例子,列表 16-6 中,那里解构 `mpsc::channel()` 的返回值为 `tx`(发送者)和 `rx`(接收者)。
### 函数参数
类似于 `let`,函数参数也可以是模式。列表 18-5 中的代码声明了一个叫做 `foo` 的函数,它获取一个 `i32` 类型的参数 `x`,这看起来应该很熟悉:
```rust
fn foo(x: i32) {
// code goes here
}
```
<span class="caption">列表 18-5: 在参数中使用模式的函数签名</span>
`x` 部分就是一个模式!类似于之前对 `let` 所做的,可以在函数参数中匹配元组。列表 18-6 展示了如何可以将传递给函数的元组拆分为值:
<span class="filename">文件名: src/main.rs</span>
```rust
fn print_coordinates(&(x, y): &(i32, i32)) {
println!("Current location: ({}, {})", x, y);
}
fn main() {
let point = (3, 5);
print_coordinates(&point);
}
```
<span class="caption">列表 18-6: 一个在参数中解构元组的函数</span>
这会打印出 `Current location: (3, 5)`。当传递值 `&(3, 5)``print_coordinates` 时,这个值会匹配模式 `&(x, y)``x` 得到了值 3`y`得到了值 5。
因为如第十三章所讲闭包类似于函数,也可以在闭包参数中使用模式。
在这些可以使用模式的位置中的一个区别是,对于 `for` 循环、`let` 和函数参数,其模式必须是 *irrefutable* 的。接下来让我们讨论这个。

@ -58,4 +58,5 @@ error[E0162]: irrefutable if-let pattern
一般来说, 多数匹配使用*refutable*模式, 除非是那种可以匹配任意值的情况使用*irrefutable*模式. `match`操作符中如果只有一个*irrefutable*模式分支也没有什么问题, 但这就没什么特别的用处, 此时可以用一个更简单的`let`语句来替换. 不管是把表达式关联到`let`语句亦或是关联到只有一个*irrefutable*模式分支的`match`操作, 代码都肯定会运行, 如果它们的表达式一样的话最终的结果也相同.
目前我们已经讨论了所有可以使用模式的地方, 也介绍了*refutable*模式和*irrefutable*模式的不同, 下面让我们一起去把可以用来创建模式的语法过目一遍吧.
目前我们已经讨论了所有可以使用模式的地方, 也介绍了*refutable*模式和*irrefutable*模式的不同, 下面让我们一起去把可以用来创建模式的语法过目一遍吧.

Loading…
Cancel
Save