Merge pull request #334 from lwintermelon/master

Improve some translations  and update some chapters to upstream.
pull/335/head
KaiserY 5 years ago committed by GitHub
commit b7dd761008
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -6,7 +6,7 @@
安全并高效的处理并发编程是 Rust 的另一个主要目标。**并发编程***Concurrent programming*),代表程序的不同部分相互独立的执行,而 **并行编程***parallel programming*代表程序不同部分于同时执行这两个概念随着计算机越来越多的利用多处理器的优势时显得愈发重要。由于历史原因在此类上下文中编程一直是困难且容易出错的Rust 希望能改变这一点。 安全并高效的处理并发编程是 Rust 的另一个主要目标。**并发编程***Concurrent programming*),代表程序的不同部分相互独立的执行,而 **并行编程***parallel programming*代表程序不同部分于同时执行这两个概念随着计算机越来越多的利用多处理器的优势时显得愈发重要。由于历史原因在此类上下文中编程一直是困难且容易出错的Rust 希望能改变这一点。
起初Rust 团队认为确保内存安全和防止并发问题是两个分别需要不同方法应对的挑战。随着时间的推移,团队发现所有权和类型系统是一系列解决内存安全 **和** 并发问题的强有力的工具!通过改进所有权和类型检查Rust 很多并发错误都是 **编译时** 错误,而非运行时错误。因此,相比花费大量时间尝试重现运行时并发 bug 出现的特定情况Rust 会拒绝编译不正确的代码并提供解释问题的错误信息。因此,你可以在开发时而不是不慎部署到生产环境后修复代码。我们给 Rust 的这一部分起了一个绰号 **无畏并发***fearless concurrency*)。无畏并发令你的代码免于出现诡异的 bug 并可以轻松重构且无需担心会引入新的 bug。 起初Rust 团队认为确保内存安全和防止并发问题是两个分别需要不同方法应对的挑战。随着时间的推移,团队发现所有权和类型系统是一系列解决内存安全 **和** 并发问题的强有力的工具!通过利用所有权和类型检查,在 Rust 中很多并发错误都是 **编译时** 错误,而非运行时错误。因此,相比花费大量时间尝试重现运行时并发 bug 出现的特定情况Rust 会拒绝编译不正确的代码并提供解释问题的错误信息。因此,你可以在开发时修复代码,而不是在部署到生产环境后修复代码。我们给 Rust 的这一部分起了一个绰号 **无畏并发***fearless concurrency*)。无畏并发令你的代码免于出现诡异的 bug 并可以轻松重构且无需担心会引入新的 bug。
> 注意:出于简洁的考虑,我们将很多问题归类为 **并发**,而不是更准确的区分 **并发和(或)并行**。如果这是一本专注于并发和/或并行的书,我们肯定会更加精确的。对于本章,当我们谈到 **并发** 时,请自行脑内替换为 **并发和(或)并行**。 > 注意:出于简洁的考虑,我们将很多问题归类为 **并发**,而不是更准确的区分 **并发和(或)并行**。如果这是一本专注于并发和/或并行的书,我们肯定会更加精确的。对于本章,当我们谈到 **并发** 时,请自行脑内替换为 **并发和(或)并行**。

@ -22,7 +22,7 @@ Rust 尝试缓和使用线程的负面影响。不过在多线程上下文中编
在当前上下文中,**运行时** 代表二进制文件中包含的由语言自身提供的代码。这些代码根据语言的不同可大可小,不过任何非汇编语言都会有一定数量的运行时代码。为此,通常人们说一个语言 “没有运行时”,一般意味着 “小运行时”。更小的运行时拥有更少的功能不过其优势在于更小的二进制输出,这使其易于在更多上下文中与其他语言相结合。虽然很多语言觉得增加运行时来换取更多功能没有什么问题,但是 Rust 需要做到几乎没有运行时,同时为了保持高性能必需能够调用 C 语言,这点也是不能妥协的。 在当前上下文中,**运行时** 代表二进制文件中包含的由语言自身提供的代码。这些代码根据语言的不同可大可小,不过任何非汇编语言都会有一定数量的运行时代码。为此,通常人们说一个语言 “没有运行时”,一般意味着 “小运行时”。更小的运行时拥有更少的功能不过其优势在于更小的二进制输出,这使其易于在更多上下文中与其他语言相结合。虽然很多语言觉得增加运行时来换取更多功能没有什么问题,但是 Rust 需要做到几乎没有运行时,同时为了保持高性能必需能够调用 C 语言,这点也是不能妥协的。
绿色线程的 M:N 模型更大的语言运行时来管理这些线程。为此Rust 标准库只提供了 1:1 线程模型实现。Rust 是足够底层的语言,所以有相应的 crate 实现了 M:N 线程模型,如果你宁愿牺牲性能来换取例如更好的线程运行控制和更低的上下文切换成本。 绿色线程的 M:N 模型需要更大的语言运行时来管理这些线程。为此Rust 标准库只提供了 1:1 线程模型实现。因为 Rust 是足够底层的语言,有一些 crate 实现了 M:N 线程模型,如果你宁愿牺牲性能来换取例如更好的线程运行控制和更低的上下文切换成本。
现在我们明白了 Rust 中的线程是如何定义的,让我们开始探索如何使用标准库提供的线程相关的 API 吧。 现在我们明白了 Rust 中的线程是如何定义的,让我们开始探索如何使用标准库提供的线程相关的 API 吧。

@ -4,7 +4,7 @@
> <br> > <br>
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f > commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
一个人气正在上升的确保安全并发的方式是 **消息传递***message passing*),这里线程或 actor 通过发送包含数据的消息来相互沟通。这个思想来源于 [Go 编程语言文档中](http://golang.org/doc/effective_go.html) 的口号“不要共享内存来通讯而是要通讯来共享内存。”“Do not communicate by 一个日益流行的确保安全并发的方式是 **消息传递***message passing*),这里线程或 actor 通过发送包含数据的消息来相互沟通。这个思想来源于 [Go 编程语言文档中](http://golang.org/doc/effective_go.html) 的口号“不要共享内存来通讯而是要通讯来共享内存。”“Do not communicate by
sharing memory; instead, share memory by communicating.”) sharing memory; instead, share memory by communicating.”)
Rust 中一个实现消息传递并发的主要工具是 **通道***channel*),一个 Rust 标准库提供了其实现的编程概念。你可以将其想象为一个水流的通道,比如河流或小溪。如果你将诸如橡皮鸭或小船之类的东西放入其中,它们会顺流而下到达下游。 Rust 中一个实现消息传递并发的主要工具是 **通道***channel*),一个 Rust 标准库提供了其实现的编程概念。你可以将其想象为一个水流的通道,比如河流或小溪。如果你将诸如橡皮鸭或小船之类的东西放入其中,它们会顺流而下到达下游。
@ -189,7 +189,7 @@ Got: the
Got: thread Got: thread
``` ```
因为在主线程中并没有任何暂停或位于 `for` 循环中用于等待的代码,所以可以说主线程是在等待从新建线程中接收值。 因为主线程中的 `for` 循环里并没有任何暂停或等待的代码,所以可以说主线程是在等待从新建线程中接收值。
### 通过克隆发送者来创建多个生产者 ### 通过克隆发送者来创建多个生产者

@ -95,8 +95,8 @@ impl AveragedCollection {
> 多态Polymorphism > 多态Polymorphism
> >
> 很多人将多态描述为继承的同义词。不过它是一个有关可以用于多种类型的代码的更广泛的概念。对于继承来说,这些类型通常是子类。 > 很多人将多态描述为继承的同义词。不过它是一个有关可以用于多种类型的代码的更广泛的概念。对于继承来说,这些类型通常是子类。
> Rust 则通过泛型来使得对多个不同类型的抽象成为可能,并通过 trait bounds 加强对这些类型所必须提供的内容的限制。这有时被称为 *bounded parametric polymorphism* > Rust 则通过泛型来对不同的可能类型进行抽象,并通过 trait bounds 对这些类型所必须提供的内容施加约束。这有时被称为 *bounded parametric polymorphism*
近来继承作为一种语言设计的解决方案在很多语言中失宠了,因为其时常带有共享多于所需的代码的风险。子类不应总是共享其父类的所有特征,但是继承却始终如此。如此会使程序设计更为不灵活,并引入无意义的子类方法调用,或由于方法实际并不适用于子类而造成错误的可能性。某些语言还只允许子类继承一个父类,进一步限制了程序设计的灵活性。 近来继承作为一种语言设计的解决方案在很多语言中失宠了,因为其时常带有共享多于所需的代码的风险。子类不应总是共享其父类的所有特征,但是继承却始终如此。如此会使程序设计更为不灵活,并引入无意义的子类方法调用,或由于方法实际并不适用于子类而造成错误的可能性。某些语言还只允许子类继承一个父类,进一步限制了程序设计的灵活性。
因为这些原因Rust 选择了一个不同的途径,使用 trait 对象替代继承。让我们看一下 Rust 中的 trait 对象是如何实现多态的。 因为这些原因Rust 选择了一个不同的途径,使用 trait 对象而不是继承。让我们看一下 Rust 中的 trait 对象是如何实现多态的。

@ -2,21 +2,21 @@
> [ch17-02-trait-objects.md](https://github.com/rust-lang/book/blob/master/src/ch17-02-trait-objects.md) > [ch17-02-trait-objects.md](https://github.com/rust-lang/book/blob/master/src/ch17-02-trait-objects.md)
> <br> > <br>
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f > commit 426f3e4ec17e539ae9905ba559411169d303a031
在第八章中,我们谈到了 vector 只能存储同种类型元素的局限。示例 8-10 中提供了一个定义 `SpreadsheetCell` 枚举来储存整型,浮点型和文本成员的替代方案。这意味着可以在每个单元中储存不同类型的数据,并仍能拥有一个代表一排单元的 vector。这在当编译代码时就知道希望可以交替使用的类型为固定集合的情况下是完全可行的。 在第八章中,我们谈到了 vector 只能存储同种类型元素的局限。示例 8-10 中提供了一个定义 `SpreadsheetCell` 枚举来储存整型,浮点型和文本成员的替代方案。这意味着可以在每个单元中储存不同类型的数据,并仍能拥有一个代表一排单元的 vector。这在当编译代码时就知道希望可以交替使用的类型为固定集合的情况下是完全可行的。
然而有时我们希望库用户在特定情况下能够扩展有效的类型集合。为了展示如何实现这一点这里将创建一个图形用户接口Graphical User Interface GUI工具的例子它通过遍历列表并调用每一个项目的 `draw` 方法来将其绘制到屏幕上 —— 此乃一个 GUI 工具的常见技术。我们将要创建一个叫做 `gui` 的库 crate它含一个 GUI 库的结构。这个 GUI 库包含一些可供开发者使用的类型,比如 `Button``TextField`。在此之上,`gui` 的用户希望创建自定义的可以绘制于屏幕上的类型:比如,一个程序员可能会增加 `Image`,另一个可能会增加 `SelectBox` 然而有时我们希望库用户在特定情况下能够扩展有效的类型集合。为了展示如何实现这一点这里将创建一个图形用户接口Graphical User Interface GUI工具的例子它通过遍历列表并调用每一个项目的 `draw` 方法来将其绘制到屏幕上 —— 此乃一个 GUI 工具的常见技术。我们将要创建一个叫做 `gui` 的库 crate它含一个 GUI 库的结构。这个 GUI 库包含一些可供开发者使用的类型,比如 `Button``TextField`。在此之上,`gui` 的用户希望创建自定义的可以绘制于屏幕上的类型:比如,一个程序员可能会增加 `Image`,另一个可能会增加 `SelectBox`
这个例子中并不会实现一个功能完善的 GUI 库,不过会展示其中各个部分是如何结合在一起的。编写库的时候,我们不可能知晓并定义所有其他程序员希望创建的类型。我们所知晓的是 `gui` 需要记录一系列不同类型的值,并需要能够对其中每一个值调用 `draw` 方法。这里无需知道调用 `draw` 方法时具体会发生什么,只需提供可供这些值调用的方法即可 这个例子中并不会实现一个功能完善的 GUI 库,不过会展示其中各个部分是如何结合在一起的。编写库的时候,我们不可能知晓并定义所有其他程序员希望创建的类型。我们所知晓的是 `gui` 需要记录一系列不同类型的值,并需要能够对其中每一个值调用 `draw` 方法。这里无需知道调用 `draw` 方法时具体会发生什么,只要该值会有那个方法可供我们调用
在拥有继承的语言中,可以定义一个名为 `Component` 的类,该类上有一个 `draw` 方法。其他的类比如 `Button`、`Image` 和 `SelectBox` 会从 `Component` 派生并因此继承 `draw` 方法。它们各自都可以覆盖 `draw` 方法来定义自己的行为,但是框架会把所有这些类型当作是 `Component` 的实例,并在其上调用 `draw`。不过 Rust 并没有继承,我们得另寻出路。 在拥有继承的语言中,可以定义一个名为 `Component` 的类,该类上有一个 `draw` 方法。其他的类比如 `Button`、`Image` 和 `SelectBox` 会从 `Component` 派生并因此继承 `draw` 方法。它们各自都可以覆盖 `draw` 方法来定义自己的行为,但是框架会把所有这些类型当作是 `Component` 的实例,并在其上调用 `draw`。不过 Rust 并没有继承,我们得另寻出路。
### 定义通用行为的 trait ### 定义通用行为的 trait
为了实现 `gui` 所期望拥有的行为,定义一个 `Draw` trait其包含名为 `draw` 的方法。接着可以定义一个存放 **trait 对象***trait object* 的 vector。trait 对象指向一个实现了我们指定 trait 的类型实例。我们通过指定某些指针,比如 `&` 引用或 `Box<T>` 智能指针,接着指定相关的 trait第十九章 “动态大小类型” 部分会介绍 trait 对象必须使用指针的原因)。我们可以使用 trait 对象代替泛型或具体类型。任何使用 trait 对象的位置Rust 的类型系统会在编译时确保任何在此上下文中使用的值会实现其 trait 对象的 trait。如此便无需在编译时就知晓所有可能的类型。 为了实现 `gui` 所期望拥有的行为,定义一个 `Draw` trait其包含名为 `draw` 的方法。接着可以定义一个存放 **trait 对象***trait object* 的 vector。trait 对象指向一个实现了我们指定 trait 的类型的实例以及一个用于在运行时查找该类型的trait方法的表。我们通过指定某种指针来创建 trait 对象,例如 `&` 引用或 `Box<T>` 智能指针,还有 `dyn` keyword 以及指定相关的 trait第十九章 “动态大小类型和`Sized` Trait” 部分会介绍 trait 对象必须使用指针的原因)。我们可以使用 trait 对象代替泛型或具体类型。任何使用 trait 对象的位置Rust 的类型系统会在编译时确保任何在此上下文中使用的值会实现其 trait 对象的 trait。如此便无需在编译时就知晓所有可能的类型。
之前提到过Rust 刻意不将结构体与枚举称为 “对象”,以便与其他语言中的对象相区别。在结构体或枚举中,结构体字段中的数据和 `impl` 块中的行为是分开的不同于其他语言中将数据和行为组合进一个称为对象的概念中。trait 对象将数据和行为两者相结合,从这种意义上说 **则** 其更类似其他语言中的对象。不过 trait 对象不同于传统的对象,因为不能向 trait 对象增加数据。trait 对象并不像其他语言中的对象那么通用trait 对象)具体的作用是允许对通用行为抽象。 之前提到过Rust 刻意不将结构体与枚举称为 “对象”,以便与其他语言中的对象相区别。在结构体或枚举中,结构体字段中的数据和 `impl` 块中的行为是分开的不同于其他语言中将数据和行为组合进一个称为对象的概念中。trait 对象将数据和行为两者相结合,从这种意义上说 **则** 其更类似其他语言中的对象。不过 trait 对象不同于传统的对象,因为不能向 trait 对象增加数据。trait 对象并不像其他语言中的对象那么通用trait 对象)具体的作用是允许对通用行为进行抽象。
示例 17-3 展示了如何定义一个带有 `draw` 方法的 trait `Draw` 示例 17-3 展示了如何定义一个带有 `draw` 方法的 trait `Draw`
@ -30,7 +30,7 @@ pub trait Draw {
<span class="caption">示例 17-3`Draw` trait 的定义</span> <span class="caption">示例 17-3`Draw` trait 的定义</span>
因为第十章已经讨论过如何定义 trait其语法看起来应该比较眼熟。接下来就是新内容了实例 17-4 定义了一个存放了名叫 `components` 的 vector 的结构体 `Screen`。这个 vector 的类型是 `Box<Draw>`,此为一个 trait 对象:它是 `Box` 中任何实现了 `Draw` trait 的类型的替身。 因为第十章已经讨论过如何定义 trait其语法看起来应该比较眼熟。接下来就是新内容了实例 17-4 定义了一个存放了名叫 `components` 的 vector 的结构体 `Screen`。这个 vector 的类型是 `Box<dyn Draw>`,此为一个 trait 对象:它是 `Box` 中任何实现了 `Draw` trait 的类型的替身。
<span class="filename">文件名: src/lib.rs</span> <span class="filename">文件名: src/lib.rs</span>
@ -97,7 +97,7 @@ impl<T> Screen<T>
这限制了 `Screen` 实例必须拥有一个全是 `Button` 类型或者全是 `TextField` 类型的组件列表。如果只需要同质(相同类型)集合,则倾向于使用泛型和 trait bound因为其定义会在编译时采用具体类型进行单态化。 这限制了 `Screen` 实例必须拥有一个全是 `Button` 类型或者全是 `TextField` 类型的组件列表。如果只需要同质(相同类型)集合,则倾向于使用泛型和 trait bound因为其定义会在编译时采用具体类型进行单态化。
另一方面,通过使用 trait 对象的方法,一个 `Screen` 实例可以存放一个既能包含 `Box<Button>`,也能包含 `Box<TextField>``Vec`。让我们看看它是如何工作的,接着会讲到其运行时性能影响。 另一方面,通过使用 trait 对象的方法,一个 `Screen` 实例可以存放一个既能包含 `Box<Button>`,也能包含 `Box<TextField>``Vec<T>`。让我们看看它是如何工作的,接着会讲到其运行时性能影响。
### 实现 trait ### 实现 trait
@ -184,7 +184,7 @@ fn main() {
当编写库的时候,我们不知道何人会在何时增加 `SelectBox` 类型,不过 `Screen` 的实现能够操作并绘制这个新类型,因为 `SelectBox` 实现了 `Draw` trait这意味着它实现了 `draw` 方法。 当编写库的时候,我们不知道何人会在何时增加 `SelectBox` 类型,不过 `Screen` 的实现能够操作并绘制这个新类型,因为 `SelectBox` 实现了 `Draw` trait这意味着它实现了 `draw` 方法。
这个概念 —— 只关心值所反映的信息而不是其具体类型 —— 类似于动态类型语言中称为 **鸭子类型***duck typing*)的概念:如果它走起来像一只鸭子,叫起来像一只鸭子,那么它就是一只鸭子!在示例 17-5 中 `Screen` 上的 `run` 实现中,`run` 并不需要知道各个组件的具体类型是什么。它并不检查组件是 `Button` 或者 `SelectBox` 的实例。通过指定 `Box<Draw>` 作为 `components` vector 中值的类型,我们就定义了 `Screen` 需要可以在其上调用 `draw` 方法的值。 这个概念 —— 只关心值所反映的信息而不是其具体类型 —— 类似于动态类型语言中称为 **鸭子类型***duck typing*)的概念:如果它走起来像一只鸭子,叫起来像一只鸭子,那么它就是一只鸭子!在示例 17-5 中 `Screen` 上的 `run` 实现中,`run` 并不需要知道各个组件的具体类型是什么。它并不检查组件是 `Button` 或者 `SelectBox` 的实例。通过指定 `Box<Draw>` 作为 `components` vector 中值的类型,我们就定义了 `Screen` 需要可以在其上调用 `draw` 方法的值。
使用 trait 对象和 Rust 类型系统来进行类似鸭子类型操作的优势是无需在运行时检查一个值是否实现了特定方法或者担心在调用时因为值没有实现方法而产生错误。如果值没有实现 trait 对象所需的 trait 则 Rust 不会编译这些代码。 使用 trait 对象和 Rust 类型系统来进行类似鸭子类型操作的优势是无需在运行时检查一个值是否实现了特定方法或者担心在调用时因为值没有实现方法而产生错误。如果值没有实现 trait 对象所需的 trait 则 Rust 不会编译这些代码。
@ -246,7 +246,7 @@ pub trait Clone {
} }
``` ```
`String` 实现了 `Clone` trait当在 `String` 实例上调用 `clone` 方法时会得到一个 `String` 实例。类似的,当调用 `Vec` 实例的 `clone` 方法会得到一个 `Vec` 实例。`clone` 的签名需要知道什么类型会代替 `Self`,因为这是它的返回值。 `String` 实现了 `Clone` trait当在 `String` 实例上调用 `clone` 方法时会得到一个 `String` 实例。类似的,当调用 `Vec<T>` 实例的 `clone` 方法会得到一个 `Vec<T>` 实例。`clone` 的签名需要知道什么类型会代替 `Self`,因为这是它的返回值。
如果尝试做一些违反有关 trait 对象的对象安全规则的事情,编译器会提示你。例如,如果尝试实现示例 17-4 中的 `Screen` 结构体来存放实现了 `Clone` trait 而不是 `Draw` trait 的类型,像这样: 如果尝试做一些违反有关 trait 对象的对象安全规则的事情,编译器会提示你。例如,如果尝试实现示例 17-4 中的 `Screen` 结构体来存放实现了 `Clone` trait 而不是 `Draw` trait 的类型,像这样:

@ -171,7 +171,7 @@ impl State for PendingReview {
<span class="caption">示例 17-15: 实现 `Post``State` trait 的 `request_review` 方法</span> <span class="caption">示例 17-15: 实现 `Post``State` trait 的 `request_review` 方法</span>
这里为 `Post` 增加一个获取 `self` 可变引用的公有方法 `request_review`。接着`Post` 当前状态内部的 `request_review` 方法而这第二个 `request_review` 方法会消费当前的状态并返回一个新状态。 这里为 `Post` 增加一个获取 `self` 可变引用的公有方法 `request_review`。接着`Post` 的当前状态下调用内部的 `request_review` 方法,并且第二个 `request_review` 方法会消费当前的状态并返回一个新状态。
这里给 `State` trait 增加了 `request_review` 方法;所有实现了这个 trait 的类型现在都需要实现 `request_review` 方法。注意不同于使用 `self``&self` 或者 `&mut self` 作为方法的第一个参数,这里使用了 `self: Box<Self>`。这个语法意味着这个方法调用只对这个类型的 `Box` 有效。这个语法获取了 `Box<Self>` 的所有权,使老状态无效化以便 `Post` 的状态值可以将自身转换为新状态。 这里给 `State` trait 增加了 `request_review` 方法;所有实现了这个 trait 的类型现在都需要实现 `request_review` 方法。注意不同于使用 `self``&self` 或者 `&mut self` 作为方法的第一个参数,这里使用了 `self: Box<Self>`。这个语法意味着这个方法调用只对这个类型的 `Box` 有效。这个语法获取了 `Box<Self>` 的所有权,使老状态无效化以便 `Post` 的状态值可以将自身转换为新状态。
@ -395,7 +395,7 @@ impl DraftPost {
<span class="caption">示例 17-19: 带有 `content` 方法的 `Post` 和没有 `content` 方法的 `DraftPost`</span> <span class="caption">示例 17-19: 带有 `content` 方法的 `Post` 和没有 `content` 方法的 `DraftPost`</span>
`Post``DraftPost` 结构体都有一个私有的 `content` 字段来储存博文的文本。这些结构体不再有 `state` 字段因为我们将类型编码为结构体的类型。`Post` 将代表发布的博文,它有一个返回 `content``content` 方法。 `Post``DraftPost` 结构体都有一个私有的 `content` 字段来储存博文的文本。这些结构体不再有 `state` 字段因为我们将状态编码改为结构体类型。`Post` 将代表发布的博文,它有一个返回 `content``content` 方法。
仍然有一个 `Post::new` 函数,不过不同于返回 `Post` 实例,它返回 `DraftPost` 的实例。现在不可能创建一个 `Post` 实例,因为 `content` 是私有的同时没有任何函数返回 `Post` 仍然有一个 `Post::new` 函数,不过不同于返回 `Post` 实例,它返回 `DraftPost` 的实例。现在不可能创建一个 `Post` 实例,因为 `content` 是私有的同时没有任何函数返回 `Post`
@ -473,6 +473,6 @@ fn main() {
## 总结 ## 总结
阅读本章后,不管你是否认为 Rust 是一个面向对象语言,现在你都见识了 trait 对象是一个 Rust 中获取部分面向对象功能的方法。动态分发可以通过牺牲少量运行时性能来为你的代码提供一些灵活性。这些灵活性可以用来实现有助于代码可维护性的面向对象模式。Rust 也有像所有权这样不同于面向对象语言的功能。面向对象模式并不总是利用 Rust 实力的最好方式,但也是可用的选项。 阅读本章后,不管你是否认为 Rust 是一个面向对象语言,现在你都见识了 trait 对象是一个 Rust 中获取部分面向对象功能的方法。动态分发可以通过牺牲少量运行时性能来为你的代码提供一些灵活性。这些灵活性可以用来实现有助于代码可维护性的面向对象模式。Rust 也有像所有权这样不同于面向对象语言的功能。面向对象模式并不总是利用 Rust 优势的最好方式,但也是可用的选项。
接下来,让我们看看另一个提供了多样灵活性的 Rust 功能:模式。贯穿全书的模式, 我们已经和它们打过照面了,但并没有见识过它们的全部本领。让我们开始探索吧! 接下来,让我们看看另一个提供了多样灵活性的 Rust 功能:模式。贯穿全书的模式, 我们已经和它们打过照面了,但并没有见识过它们的全部本领。让我们开始探索吧!

@ -2,7 +2,7 @@
> [ch18-03-pattern-syntax.md](https://github.com/rust-lang/book/blob/master/src/ch18-03-pattern-syntax.md) > [ch18-03-pattern-syntax.md](https://github.com/rust-lang/book/blob/master/src/ch18-03-pattern-syntax.md)
> <br> > <br>
> commit bc6d44e5d2cc2ec291c3c93ee5a25b4a634a4403 > commit c231bf7e49446e78b147a814323d8f25013a605b
通过本书我们已领略过许多不同类型模式的例子。本节会统一列出所有在模式中有效的语法并且会阐述你为什么可能会希望使用其中的每一个。 通过本书我们已领略过许多不同类型模式的例子。本节会统一列出所有在模式中有效的语法并且会阐述你为什么可能会希望使用其中的每一个。
@ -25,7 +25,7 @@ match x {
### 匹配命名变量 ### 匹配命名变量
命名变量是匹配任何值的不可反驳模式,这在之前已经使用过数次。然而当其用于 `match` 表达式时情况会有些复杂。因为 `match` 会开始一个新作用域,`match` 表达式中作为模式的一部分声明的变量会覆盖 `match` 结构之外的同名变量,与所有变量一样。在示例 18-11 中,声明了一个值为 `Some(5)` 的变量 `x` 和一个值为 `10` 的变量 `y`。接着在值 `x` 上创建了一个 `match` 表达式。观察匹配分支中的模式和结尾的 `println!`,并尝试在运行代码之前计算出会打印什么,或者继续阅读: 命名变量是匹配任何值的不可反驳模式,这在之前已经使用过数次。然而当其用于 `match` 表达式时情况会有些复杂。因为 `match` 会开始一个新作用域,`match` 表达式中作为模式的一部分声明的变量会覆盖 `match` 结构之外的同名变量,与所有变量一样。在示例 18-11 中,声明了一个值为 `Some(5)` 的变量 `x` 和一个值为 `10` 的变量 `y`。接着在值 `x` 上创建了一个 `match` 表达式。观察匹配分支中的模式和结尾的 `println!`,并在运行此代码或进一步阅读之前推断这段代码会打印什么。
<span class="filename">文件名: src/main.rs</span> <span class="filename">文件名: src/main.rs</span>
@ -54,7 +54,7 @@ fn main() {
一旦 `match` 表达式执行完毕,其作用域也就结束了,同理内部 `y` 的作用域也结束了。最后的 `println!` 会打印 `at the end: x = Some(5), y = 10` 一旦 `match` 表达式执行完毕,其作用域也就结束了,同理内部 `y` 的作用域也结束了。最后的 `println!` 会打印 `at the end: x = Some(5), y = 10`
为了创建能够比较外部 `x``y` 的值,而不引入覆盖变量的 `match` 表达式我们需要相应的使用带有条件的匹配守卫match guard“匹配守卫提供的额外条件” 会讨论匹配守卫。 为了创建能够比较外部 `x``y` 的值,而不引入覆盖变量的 `match` 表达式我们需要相应的使用带有条件的匹配守卫match guard我们稍后将在“匹配守卫提供的额外条件” 这一小节讨论匹配守卫。
### 多个模式 ### 多个模式
@ -87,7 +87,7 @@ match x {
如果 `x` 是 1、2、3、4 或 5第一个分支就会匹配。这相比使用 `|` 运算符表达相同的意思更为方便;相比 `1 ... 5`,使用 `|` 则不得不指定 `1 | 2 | 3 | 4 | 5`。相反指定范围就简短的多,特别是在希望匹配比如从 1 到 1000 的数字的时候! 如果 `x` 是 1、2、3、4 或 5第一个分支就会匹配。这相比使用 `|` 运算符表达相同的意思更为方便;相比 `1 ... 5`,使用 `|` 则不得不指定 `1 | 2 | 3 | 4 | 5`。相反指定范围就简短的多,特别是在希望匹配比如从 1 到 1000 的数字的时候!
范围只允许用于数字或 `char` 值,因为编译器会在编译时检查范围不为空。`char` 和 数字值是 Rust 唯一知道范围是否为空的类型。 范围只允许用于数字或 `char` 值,因为编译器会在编译时检查范围不为空。`char` 和 数字值是 Rust 仅有的可以判断范围是否为空的类型。
如下是一个使用 `char` 类型值范围的例子: 如下是一个使用 `char` 类型值范围的例子:
@ -130,7 +130,7 @@ fn main() {
<span class="caption">示例 18-12: 解构一个结构体的字段为单独的变量</span> <span class="caption">示例 18-12: 解构一个结构体的字段为单独的变量</span>
这段代码创建了变量 `a``b` 来匹配变量 `p` 中的 `x``y` 字段。这个例子展示了模式中的变量名不必与结构体中的字段名一致。不过通常希望变量名与字段名一致以便于理解变量来自于哪些字段。 这段代码创建了变量 `a``b` 来匹配结构体 `p` 中的 `x``y` 字段。这个例子展示了模式中的变量名不必与结构体中的字段名一致。不过通常希望变量名与字段名一致以便于理解变量来自于哪些字段。
因为变量名匹配字段名是常见的,同时因为 `let Point { x: x, y: y } = p;` 包含了很多重复,所以对于匹配结构体字段的模式存在简写:只需列出结构体字段的名称,则模式创建的变量会有相同的名称。示例 18-13 展示了与示例 18-12 有着相同行为的代码,不过 `let` 模式创建的变量为 `x``y` 而不是 `a``b` 因为变量名匹配字段名是常见的,同时因为 `let Point { x: x, y: y } = p;` 包含了很多重复,所以对于匹配结构体字段的模式存在简写:只需列出结构体字段的名称,则模式创建的变量会有相同的名称。示例 18-13 展示了与示例 18-12 有着相同行为的代码,不过 `let` 模式创建的变量为 `x``y` 而不是 `a``b`
@ -155,7 +155,7 @@ fn main() {
这段代码创建了变量 `x``y`,与变量 `p` 中的 `x``y` 相匹配。其结果是变量 `x``y` 包含结构体 `p` 中的值。 这段代码创建了变量 `x``y`,与变量 `p` 中的 `x``y` 相匹配。其结果是变量 `x``y` 包含结构体 `p` 中的值。
也可以在部分结构体模式中使用字面值进行结构,而不是为所有的字段创建变量。这允许我们测试一些字段为特定值的同时创建其他字段的变量。 也可以使用字面值作为结构体模式的一部分进行进行解构,而不是为所有的字段创建变量。这允许我们测试一些字段为特定值的同时创建其他字段的变量。
示例 18-14 展示了一个 `match` 语句将 `Point` 值分成了三种情况:直接位于 `x` 轴上(此时 `y = 0` 为真)、位于 `y` 轴上(`x = 0`)或不在任何轴上的点。 示例 18-14 展示了一个 `match` 语句将 `Point` 值分成了三种情况:直接位于 `x` 轴上(此时 `y = 0` 为真)、位于 `y` 轴上(`x = 0`)或不在任何轴上的点。
@ -237,16 +237,16 @@ fn main() {
对于像 `Message::Write` 这样的包含一个元素,以及像 `Message::ChangeColor` 这样包含两个元素的类元组枚举成员,其模式则类似于用于解构元组的模式。模式中变量的数量必须与成员中元素的数量一致。 对于像 `Message::Write` 这样的包含一个元素,以及像 `Message::ChangeColor` 这样包含两个元素的类元组枚举成员,其模式则类似于用于解构元组的模式。模式中变量的数量必须与成员中元素的数量一致。
#### 解构嵌套的结构体 & 枚举 #### 解构嵌套的结构体枚举
目前为止,所有的例子都只匹配了深度为一级的结构体。当然也可以匹配嵌套的结构体 目前为止,所有的例子都只匹配了深度为一级的结构体或枚举。当然也可以匹配嵌套的项
我们可以重构上面的例子来同时支持 RGB 和 HSV 色彩模式: 例如,我们可以重构列表 18-15 的代码来同时支持 RGB 和 HSV 色彩模式:
```rust ```rust
enum Color { enum Color {
Rgb(i32, i32, i32), Rgb(i32, i32, i32),
Hsv(i32, i32, i32) Hsv(i32, i32, i32)
} }
enum Message { enum Message {
@ -281,52 +281,13 @@ fn main() {
} }
``` ```
#### 解构引用 <span class="caption">示例 18-16: 匹配嵌套的枚举</span>
当模式所匹配的值中包含引用时,需要解构引用之中的值,这可以通过在模式中指定 `&` 做到。这让我们得到一个包含引用所指向数据的变量,而不是包含引用的变量。这个技术在通过迭代器遍历引用时,我们需要使用闭包中的值而不是其引用时非常有用。 `match` 表达式第一个分支的模式匹配一个包含 `Color::Rgb` 枚举成员的 `Message::ChangeColor` 枚举成员然后模式绑定了3个内部的 `i32` 值。第二个分支的模式也匹配一个 `Message::ChangeColor` 枚举成员, 但是其内部的枚举会匹配 `Color::Hsv` 枚举成员。 我们可以在一个 `match` 表达式中指定这些复杂条件,即使会涉及到两个枚举。
示例 18-16 中的例子遍历一个 vector 中的 `Point` 实例的引用,并同时解构引用和其中的结构体以方便对 `x``y` 值进行计算:
```rust
# struct Point {
# x: i32,
# y: i32,
# }
#
let points = vec![
Point { x: 0, y: 0 },
Point { x: 1, y: 5 },
Point { x: 10, y: -3 },
];
let sum_of_squares: i32 = points
.iter()
.map(|&Point { x, y }| x * x + y * y)
.sum();
```
<span class="caption">示例 18-16: 将结构体的引用解构到其字段值中</span>
这段代码的结果是变量 `sum_of_squares` 的值为 135这个结果是将 `points` vector 中每一个 `Point``x``y` 的平方相加后求和得到的数字。
如果没有在 `&Point { x, y }` 中包含 `&` 则会得到一个类型不匹配错误,因为这样 `iter` 会遍历 vector 中项的引用而不是值本身。这个错误看起来像这样:
```text
error[E0308]: mismatched types
-->
|
14 | .map(|Point { x, y }| x * x + y * y)
| ^^^^^^^^^^^^ expected &Point, found struct `Point`
|
= note: expected type `&Point`
found type `Point`
```
这个错误表明 Rust 期望闭包匹配 `&Point`,不过我们尝试直接匹配 `Point` 值,而不是 `Point` 的引用。
#### 解构结构体和元组 #### 解构结构体和元组
甚至可以用复杂的方式来合、匹配和嵌套解构模式。如下是一个负责结构体的例子,其中结构体和元组嵌套在元组中,并将所有的原始类型解构出来: 甚至可以用复杂的方式来混合、匹配和嵌套解构模式。如下是一个负责结构体的例子,其中结构体和元组嵌套在元组中,并将所有的原始类型解构出来:
```rust ```rust
# struct Point { # struct Point {
@ -369,7 +330,7 @@ fn main() {
#### 使用嵌套的 `_` 忽略部分值 #### 使用嵌套的 `_` 忽略部分值
当只需要测试部分值但在期望运行的代码部分中没有使用它们时,也可以在另一个模式内部使用 `_` 来只忽略部分值。示例 18-18 展示了负责从设置中获取一个值的代码。业务需求是用户不允许覆盖某个设置中已经存在的自定义配置,但是可以重设设置和在目前未设置时提供新的设置。 也可以在一个模式内部使用`_` 忽略部分值,例如,当只需要测试部分值但在期望运行的代码中没有用到其他部分时。示例 18-18 展示了负责管理设置值的代码。业务需求是用户不允许覆盖现有的自定义设置,但是可以取消设置,也可以在当前未设置时为其提供设置。
```rust ```rust
let mut setting_value = Some(5); let mut setting_value = Some(5);
@ -387,9 +348,9 @@ match (setting_value, new_setting_value) {
println!("setting is {:?}", setting_value); println!("setting is {:?}", setting_value);
``` ```
<span class="caption">使用 18-18: 当不需要 `Some` 中的值时在模式内使用下划线来匹配 `Some` 成员</span> <span class="caption">示例 18-18: 当不需要 `Some` 中的值时在模式内使用下划线来匹配 `Some` 成员</span>
这段代码会打印出 `Can't overwrite an existing customized value` 接着是 `setting is Some(5)`。在第一个匹配分支,我们不需要匹配或使用任一个 `Some` 成员中的值;重要的部分是需要测试 `setting_value``new_setting_value` 都为 `Some` 成员的情况。在这种情况,我们希望打印出为何不改变 `setting_value`,并且不会改变它。 这段代码会打印出 `Can't overwrite an existing customized value` 接着是 `setting is Some(5)`。在第一个匹配分支,我们不需要匹配或使用任一个 `Some` 成员中的值;重要的部分是需要测试 `setting_value``new_setting_value` 都为 `Some` 成员的情况。在这种情况,我们打印出为何不改变 `setting_value`,并且不会改变它。
对于所有其他情况(`setting_value` 或 `new_setting_value` 任一为 `None`),这由第二个分支的 `_` 模式体现,这时确实希望允许 `new_setting_value` 变为 `setting_value` 对于所有其他情况(`setting_value` 或 `new_setting_value` 任一为 `None`),这由第二个分支的 `_` 模式体现,这时确实希望允许 `new_setting_value` 变为 `setting_value`
@ -498,7 +459,7 @@ fn main() {
这里用 `first``last` 来匹配第一个和最后一个值。`..` 将匹配并忽略中间的所有值。 这里用 `first``last` 来匹配第一个和最后一个值。`..` 将匹配并忽略中间的所有值。
然而使用 `..` 必须是无歧义的。如果期望匹配和忽略的值是不明确的Rust 会报错。示例 18-25 展示了一个带有歧义的 `..` 应用,因此其不能编译: 然而使用 `..` 必须是无歧义的。如果期望匹配和忽略的值是不明确的Rust 会报错。示例 18-25 展示了一个带有歧义的 `..` 例子,因此其不能编译:
<span class="filename">文件名: src/main.rs</span> <span class="filename">文件名: src/main.rs</span>
@ -530,7 +491,7 @@ Rust 不可能决定在元组中匹配 `second` 值之前应该忽略多少个
### 匹配守卫提供的额外条件 ### 匹配守卫提供的额外条件
**匹配守卫***match guard*)是一个指定 `match` 分支模式之后的额外 `if` 条件,它也必须被满足才能选择此分支。匹配守卫用于表达比单独的模式所能允许的更为复杂的情况。 **匹配守卫***match guard*)是一个指定 `match` 分支模式之后的额外 `if` 条件,它也必须被满足才能选择此分支。匹配守卫用于表达比单独的模式所能允许的更为复杂的情况。
这个条件可以使用模式中创建的变量。示例 18-26 展示了一个 `match`,其中第一个分支有模式 `Some(x)` 还有匹配守卫 `if x < 5` 这个条件可以使用模式中创建的变量。示例 18-26 展示了一个 `match`,其中第一个分支有模式 `Some(x)` 还有匹配守卫 `if x < 5`
@ -552,7 +513,7 @@ match num {
无法在模式中表达 `if x < 5` 的条件,所以匹配守卫提供了表现此逻辑的能力。 无法在模式中表达 `if x < 5` 的条件,所以匹配守卫提供了表现此逻辑的能力。
在示例 18-11 中,我们提到可以使用匹配守卫来解决模式中变量覆盖的问题,那里 `match` 表达式的模式中新建了一个变量而不是使用 `match` 之外的同名变量。新变量意味着不能够测试外部变量的值。实例 18-27 展示了如何使用匹配守卫修复这个问题: 在示例 18-11 中,我们提到可以使用匹配守卫来解决模式中变量覆盖的问题,那里 `match` 表达式的模式中新建了一个变量而不是使用 `match` 之外的同名变量。新变量意味着不能够测试外部变量的值。示例 18-27 展示了如何使用匹配守卫修复这个问题。
<span class="filename">文件名: src/main.rs</span> <span class="filename">文件名: src/main.rs</span>
@ -575,9 +536,9 @@ fn main() {
现在这会打印出 `Default case, x = Some(5)`。现在第二个匹配分支中的模式不会引入一个覆盖外部 `y` 的新变量 `y`,这意味着可以在匹配守卫中使用外部的 `y`。相比指定会覆盖外部 `y` 的模式 `Some(y)`,这里指定为 `Some(n)`。此新建的变量 `n` 并没有覆盖任何值,因为 `match` 外部没有变量 `n` 现在这会打印出 `Default case, x = Some(5)`。现在第二个匹配分支中的模式不会引入一个覆盖外部 `y` 的新变量 `y`,这意味着可以在匹配守卫中使用外部的 `y`。相比指定会覆盖外部 `y` 的模式 `Some(y)`,这里指定为 `Some(n)`。此新建的变量 `n` 并没有覆盖任何值,因为 `match` 外部没有变量 `n`
匹配守卫 `if n == y` 中,这并不是一个模式所以没有引入新变量。这个 `y` **正是** 外部的 `y` 而不是新的覆盖变量 `y`,这样就可以通过比较 `n``y` 来表达寻找一个与外部 `y` 相同的值的概念了。 匹配守卫 `if n == y` 并不是一个模式所以没有引入新变量。这个 `y` **正是** 外部的 `y` 而不是新的覆盖变量 `y`,这样就可以通过比较 `n``y` 来表达寻找一个与外部 `y` 相同的值的概念了。
也可以在匹配守卫中使用 **或** 运算符 `|` 来指定多个模式,同时匹配守卫的条件会作用所有的模式。示例 18-28 展示了结合匹配守卫与使用了 `|` 的模式的优先级。这个例子中重要的部分是匹配守卫 `if y` 作用于 `4`、`5` **和** `6`,即使这看起来好像 `if y` 只作用于 `6` 也可以在匹配守卫中使用 **或** 运算符 `|` 来指定多个模式,同时匹配守卫的条件会作用所有的模式。示例 18-28 展示了结合匹配守卫与使用了 `|` 的模式的优先级。这个例子中重要的部分是匹配守卫 `if y` 作用于 `4`、`5` **和** `6`,即使这看起来好像 `if y` 只作用于 `6`
```rust ```rust
let x = 4; let x = 4;
@ -633,9 +594,9 @@ match msg {
上例会打印出 `Found an id in range: 5`。通过在 `3...7` 之前指定 `id_variable @`,我们捕获了任何匹配此范围的值并同时测试其值匹配这个范围模式。 上例会打印出 `Found an id in range: 5`。通过在 `3...7` 之前指定 `id_variable @`,我们捕获了任何匹配此范围的值并同时测试其值匹配这个范围模式。
第二个分支只在模式中指定了一个范围,分支相关代码代码没有一个包含 `id` 字段实际值的变量。`id` 字段的值将会是 10、11 或 12不过这个模式的代码并不知情也不能使用 `id` 字段中的值,因为没有将 `id` 值保存进一个变量。 第二个分支只在模式中指定了一个范围,分支相关代码代码没有一个包含 `id` 字段实际值的变量。`id` 字段的值可以是 10、11 或 12不过这个模式的代码并不知情也不能使用 `id` 字段中的值,因为没有将 `id` 值保存进一个变量。
最后一个分支指定了一个没有范围的变量,此时确实拥有可以用于分支代码的变量 `id`,因为这里使用了结构体字段简写语法。不过此分支中不能像头两个分支那样对 `id` 字段的值进行任何测试:任何值都会匹配此分支。 最后一个分支指定了一个没有范围的变量,此时确实拥有可以用于分支代码的变量 `id`,因为这里使用了结构体字段简写语法。不过此分支中没有像头两个分支那样对 `id` 字段的值进行测试:任何值都会匹配此分支。
使用 `@` 可以在一个模式中同时测试和保存变量值。 使用 `@` 可以在一个模式中同时测试和保存变量值。
@ -690,4 +651,4 @@ println!("robot_name is: {:?}", robot_name);
模式是 Rust 中一个很有用的功能,它帮助我们区分不同类型的数据。当用于 `match` 语句时Rust 确保模式会包含每一个可能的值,否则程序将不能编译。`let` 语句和函数参数的模式使得这些结构更强大,可以在将值解构为更小部分的同时为变量赋值。可以创建简单或复杂的模式来满足我们的要求。 模式是 Rust 中一个很有用的功能,它帮助我们区分不同类型的数据。当用于 `match` 语句时Rust 确保模式会包含每一个可能的值,否则程序将不能编译。`let` 语句和函数参数的模式使得这些结构更强大,可以在将值解构为更小部分的同时为变量赋值。可以创建简单或复杂的模式来满足我们的要求。
现在,作为本书的倒数第二个章节,让我们看看一些 Rust 众多功能中较为高级的部分。 接下来,在本书倒数第二章中,我们将介绍一些 Rust 众多功能中较为高级的部分。

Loading…
Cancel
Save