Merge pull request #373 from orzyyyy/typo

fix: typo
This commit is contained in:
KaiserY 2020-02-03 10:04:16 +08:00 committed by GitHub
commit 74b24b1ee5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 111 additions and 124 deletions

View File

@ -79,7 +79,7 @@ Cargo 编译并运行了测试。在 `Compiling`、`Finished` 和 `Running` 这
[bench]: https://doc.rust-lang.org/unstable-book/library-features/test.html
测试输出中以 `Doc-tests adder` 开头的这一部分是所有文档测试的结果。我们现在并没有任何文档测试,不过 Rust 会编译任何在 API 文档中的代码示例。这个功能帮助我们使文档和代码保持同步!在第十四章的 [“文档注释作为测试”][doc-comments] 部分会讲到如何编写文档测试。现在我们将忽略 `Doc-tests` 部分的输出。
测试输出中`Doc-tests adder` 开头的这一部分是所有文档测试的结果。我们现在并没有任何文档测试,不过 Rust 会编译任何在 API 文档中的代码示例。这个功能帮助我们使文档和代码保持同步!在第十四章的 [“文档注释作为测试”][doc-comments] 部分会讲到如何编写文档测试。现在我们将忽略 `Doc-tests` 部分的输出。
让我们改变测试的名称并看看这如何改变测试的输出。给 `it_works` 函数起个不同的名字,比如 `exploration`,像这样:

View File

@ -12,7 +12,7 @@
第三个问题是如果打开文件失败我们使用 `expect` 来打印出错误信息,不过这个错误信息只是说 `file not found`。除了缺少文件之外还有很多可以导致打开文件失败的方式:例如,文件可能存在,不过可能没有打开它的权限。如果我们现在就出于这种情况,打印出的 `file not found` 错误信息就给了用户错误的建议!
第四,我们不停使用 `expect` 来处理不同的错误,如果用户没有指定足够的参数来运行程序,他们会从 Rust 得到 `index out of bounds` 错误,而这并不能明确解释问题。如果所有的错误处理都位于一处这样将来的维护者在需要修改错误处理逻辑时就只需要考虑这一处代码。将所有的错误处理都放在一处也有助于确保我们打印的错误信息对终端用户来说是有意义的。
第四,我们不停使用 `expect` 来处理不同的错误,如果用户没有指定足够的参数来运行程序,他们会从 Rust 得到 `index out of bounds` 错误,而这并不能明确解释问题。如果所有的错误处理都位于一处这样将来的维护者在需要修改错误处理逻辑时就只需要考虑这一处代码。将所有的错误处理都放在一处也有助于确保我们打印的错误信息对终端用户来说是有意义的。
让我们通过重构项目来解决这些问题。

View File

@ -1,7 +1,6 @@
## 将 crate 发布到 Crates.io
> [ch14-02-publishing-to-crates-io.md](https://github.com/rust-lang/book/blob/master/src/ch14-02-publishing-to-crates-io.md)
> <br>
> [ch14-02-publishing-to-crates-io.md](https://github.com/rust-lang/book/blob/master/src/ch14-02-publishing-to-crates-io.md) > <br>
> commit c084bdd9ee328e7e774df19882ccc139532e53d8
我们曾经在项目中使用 [crates.io](https://crates.io)<!-- ignore --> 上的包作为依赖,不过你也可以通过发布自己的包来向它人分享代码。[crates.io](https://crates.io)<!-- ignore --> 用来分发包的源代码,所以它主要托管开源代码。
@ -10,13 +9,13 @@ Rust 和 Cargo 有一些帮助它人更方便找到和使用你发布的包的
### 编写有用的文档注释
准确的包文档有助于其他用户理解如何以及何时使用他们,所以花一些时间编写文档是值得的。第三章中我们讨论了如何使用两斜杠 `//` 注释 Rust 代码。Rust 也有特定的用于文档的注释类型,通常被称为 **文档注释***documentation comments*),他们会生成 HTML 文档。这些 HTML 展示公有 API 文档注释的内容,他们意在让对库感兴趣的程序员理解如何 **使用** 这个 crate而不是它是如何被 **实现** 的。
准确的包文档有助于其他用户理解如何以及何时使用他们,所以花一些时间编写文档是值得的。第三章中我们讨论了如何使用两斜杠 `//` 注释 Rust 代码。Rust 也有特定的用于文档的注释类型,通常被称为 **文档注释**_documentation comments_),他们会生成 HTML 文档。这些 HTML 展示公有 API 文档注释的内容,他们意在让对库感兴趣的程序员理解如何 **使用** 这个 crate而不是它是如何被 **实现** 的。
文档注释使用三斜杠 `///` 而不是两斜杆支持 Markdown 注解来格式化文本。文档注释就位于需要文档的项的之前。示例 14-1 展示了一个 `my_crate` crate 中 `add_one` 函数的文档注释:
文档注释使用三斜杠 `///` 而不是两斜杆支持 Markdown 注解来格式化文本。文档注释就位于需要文档的项的之前。示例 14-1 展示了一个 `my_crate` crate 中 `add_one` 函数的文档注释:
<span class="filename">文件名: src/lib.rs</span>
```rust,ignore
````rust,ignore
/// 将给定的数字加一
///
/// # Examples
@ -30,11 +29,11 @@ Rust 和 Cargo 有一些帮助它人更方便找到和使用你发布的包的
pub fn add_one(x: i32) -> i32 {
x + 1
}
```
````
<span class="caption">示例 14-1一个函数的文档注释</span>
这里,我们提供了一个 `add_one` 函数工作的描述,接着开始了一个标题为 `Examples` 的部分,和展示如何使用 `add_one` 函数的代码。可以运行 `cargo doc` 来生成这个文档注释的 HTML 文档。这个命令运行由 Rust 分发的工具 `rustdoc` 并将生成的 HTML 文档放入 *target/doc* 目录。
这里,我们提供了一个 `add_one` 函数工作的描述,接着开始了一个标题为 `Examples` 的部分,和展示如何使用 `add_one` 函数的代码。可以运行 `cargo doc` 来生成这个文档注释的 HTML 文档。这个命令运行由 Rust 分发的工具 `rustdoc` 并将生成的 HTML 文档放入 _target/doc_ 目录。
为了方便起见,运行 `cargo doc --open` 会构建当前 crate 文档(同时还有所有 crate 依赖的文档)的 HTML 并在浏览器中打开。导航到 `add_one` 函数将会发现文档注释的文本是如何渲染的,如图 14-1 所示:
@ -69,9 +68,9 @@ test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
#### 注释包含项的结构
还有另一种风格的文档注释,`//!`,这为包含注释的项,而不是注释之后的项增加文档。这通常用于 crate 根文件(通常是 *src/lib.rs*)或模块的根文件为 crate 或模块整体提供文档。
还有另一种风格的文档注释,`//!`,这为包含注释的项,而不是注释之后的项增加文档。这通常用于 crate 根文件(通常是 _src/lib.rs_)或模块的根文件为 crate 或模块整体提供文档。
作为一个例子,如果我们希望增加描述包含 `add_one` 函数的 `my_crate` crate 目的的文档,可以在 *src/lib.rs* 开头增加以 `//!` 开头的注释,如示例 14-2 所示:
作为一个例子,如果我们希望增加描述包含 `add_one` 函数的 `my_crate` crate 目的的文档,可以在 _src/lib.rs_ 开头增加以 `//!` 开头的注释,如示例 14-2 所示:
<span class="filename">文件名: src/lib.rs</span>
@ -87,7 +86,7 @@ test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
<span class="caption">示例 14-2`my_crate` crate 整体的文档</span>
注意 `//!` 的最后一行之后没有任何代码。因为他们以 `//!` 开头而不是 `///`,这是属于包含此注释的项而不是注释之后项的文档。在这个情况中,包含这个注释的项是 *src/lib.rs* 文件,也就是 crate 根文件。这些注释描述了整个 crate。
注意 `//!` 的最后一行之后没有任何代码。因为他们以 `//!` 开头而不是 `///`,这是属于包含此注释的项而不是注释之后项的文档。在这个情况中,包含这个注释的项是 _src/lib.rs_ 文件,也就是 crate 根文件。这些注释描述了整个 crate。
如果运行 `cargo doc --open`,将会发现这些注释显示在 `my_crate` 文档的首页,位于 crate 中公有项列表之上,如图 14-2 所示:
@ -229,13 +228,13 @@ fn main() {
$ cargo login abcdefghijklmnopqrstuvwxyz012345
```
这个命令会通知 Cargo 你的 API token 并将其储存在本地的 *~/.cargo/credentials* 文件中。注意这个 token 是一个 **秘密****secret**)且不应该与其他人共享。如果因为任何原因与他人共享了这个信息,应该立即到 [crates.io](https://crates.io)<!-- ignore --> 重新生成这个 token。
这个命令会通知 Cargo 你的 API token 并将其储存在本地的 _~/.cargo/credentials_ 文件中。注意这个 token 是一个 **秘密****secret**)且不应该与其他人共享。如果因为任何原因与他人共享了这个信息,应该立即到 [crates.io](https://crates.io)<!-- ignore --> 重新生成这个 token。
### 发布新 crate 之前
有了账号之后,比如说你已经有一个希望发布的 crate。在发布之前你需要在 crate 的 *Cargo.toml* 文件的 `[package]` 部分增加一些本 crate 的元信息metadata
有了账号之后,比如说你已经有一个希望发布的 crate。在发布之前你需要在 crate 的 _Cargo.toml_ 文件的 `[package]` 部分增加一些本 crate 的元信息metadata
首先 crate 需要一个唯一的名称。虽然在本地开发 crate 时,可以使用任何你喜欢的名称。不过 [crates.io](https://crates.io)<!-- ignore --> 上的 crate 名称遵守先到先得的分配原则。一旦某个 crate 名称被使用,其他人就不能再发布这个名称的 crate 了。请在网站上搜索你希望使用的名称来找出它是否已被使用。如果没有,修改 *Cargo.toml*`[package]` 里的名称为你希望用于发布的名称,像这样:
首先 crate 需要一个唯一的名称。虽然在本地开发 crate 时,可以使用任何你喜欢的名称。不过 [crates.io](https://crates.io)<!-- ignore --> 上的 crate 名称遵守先到先得的分配原则。一旦某个 crate 名称被使用,其他人就不能再发布这个名称的 crate 了。请在网站上搜索你希望使用的名称来找出它是否已被使用。如果没有,修改 _Cargo.toml_`[package]` 里的名称为你希望用于发布的名称,像这样:
<span class="filename">文件名: Cargo.toml</span>
@ -255,9 +254,9 @@ homepage or repository.
error: api errors: missing or empty metadata fields: description, license.
```
这是因为我们缺少一些关键信息:关于该 crate 用途的描述和用户可能在何种条款下使用该 crate 的 license。为了修正这个错误需要在 *Cargo.toml* 中引入这些信息。
这是因为我们缺少一些关键信息:关于该 crate 用途的描述和用户可能在何种条款下使用该 crate 的 license。为了修正这个错误需要在 _Cargo.toml_ 中引入这些信息。
描述通常是一两句话,因为它会出现在 crate 的搜索结果中和 crate 页面里。对于 `license` 字段,你需要一个 **license 标识符值***license identifier value*)。[Linux 基金会的 Software Package Data Exchange (SPDX)][spdx] 列出了可以使用的标识符。例如,为了指定 crate 使用 MIT License增加 `MIT` 标识符:
描述通常是一两句话,因为它会出现在 crate 的搜索结果中和 crate 页面里。对于 `license` 字段,你需要一个 **license 标识符值**_license identifier value_)。[Linux 基金会的 Software Package Data Exchange (SPDX)][spdx] 列出了可以使用的标识符。例如,为了指定 crate 使用 MIT License增加 `MIT` 标识符:
[spdx]: http://spdx.org/licenses/
@ -273,7 +272,7 @@ license = "MIT"
关于项目所适用的 license 指导超出了本书的范畴。很多 Rust 社区成员选择与 Rust 自身相同的 license这是一个双许可的 `MIT OR Apache-2.0`。这个实践展示了也可以通过 `OR` 分隔为项目指定多个 license 标识符。
那么,有了唯一的名称、版本号、由 `cargo new` 新建项目时增加的作者信息、描述和所选择的 license已经准备好发布的项目的 *Cargo.toml* 文件可能看起来像这样:
那么,有了唯一的名称、版本号、由 `cargo new` 新建项目时增加的作者信息、描述和所选择的 license已经准备好发布的项目的 _Cargo.toml_ 文件可能看起来像这样:
<span class="filename">文件名: Cargo.toml</span>
@ -295,7 +294,7 @@ license = "MIT OR Apache-2.0"
现在我们创建了一个账号,保存了 API token为 crate 选择了一个名字,并指定了所需的元数据,你已经准备好发布了!发布 crate 会上传特定版本的 crate 到 [crates.io](https://crates.io)<!-- ignore --> 以供他人使用。
发布 crate 时请多加小心,因为发布是 **永久性的***permanent*)。对应版本不可能被覆盖,其代码也不可能被删除。[crates.io](https://crates.io)<!-- ignore --> 的一个主要目标是作为一个存储代码的永久文档服务器,这样所有依赖 [crates.io](https://crates.io)<!-- ignore --> 中的 crate 的项目都能一直正常工作。而允许删除版本没办法达成这个目标。然而,可以被发布的版本号却没有限制。
发布 crate 时请多加小心,因为发布是 **永久性的**_permanent_)。对应版本不可能被覆盖,其代码也不可能被删除。[crates.io](https://crates.io)<!-- ignore --> 的一个主要目标是作为一个存储代码的永久文档服务器,这样所有依赖 [crates.io](https://crates.io)<!-- ignore --> 中的 crate 的项目都能一直正常工作。而允许删除版本没办法达成这个目标。然而,可以被发布的版本号却没有限制。
再次运行 `cargo publish` 命令。这次它应该会成功:
@ -314,15 +313,15 @@ Uploading guessing_game v0.1.0 (file:///projects/guessing_game)
### 发布现存 crate 的新版本
当你修改了 crate 并准备好发布新版本时,改变 *Cargo.toml*`version` 所指定的值。请使用 [语义化版本规则][semver] 来根据修改的类型决定下一个版本号。接着运行 `cargo publish` 来上传新版本。
当你修改了 crate 并准备好发布新版本时,改变 _Cargo.toml_`version` 所指定的值。请使用 [语义化版本规则][semver] 来根据修改的类型决定下一个版本号。接着运行 `cargo publish` 来上传新版本。
[semver]: http://semver.org/
### 使用 `cargo yank` 从 Crates.io 撤回版本
虽然你不能删除之前版本的 crate但是可以阻止任何将来的项目将他们加入到依赖中。这在某个版本因为这样或那样的原因被破坏的情况很有用。对于这种情况Cargo 支持 **撤回***yanking*)某个版本。
虽然你不能删除之前版本的 crate但是可以阻止任何将来的项目将他们加入到依赖中。这在某个版本因为这样或那样的原因被破坏的情况很有用。对于这种情况Cargo 支持 **撤回**_yanking_)某个版本。
撤回某个版本会阻止新项目开始依赖此版本,不过所有现存此依赖的项目仍然能够下载和依赖这个版本。从本质上说,撤回意味着所有带有 *Cargo.lock* 的项目的依赖不会被破坏,同时任何新生成的 *Cargo.lock* 将不能使用被撤回的版本。
撤回某个版本会阻止新项目开始依赖此版本,不过所有现存此依赖的项目仍然能够下载和依赖这个版本。从本质上说,撤回意味着所有带有 _Cargo.lock_ 的项目的依赖不会被破坏,同时任何新生成的 _Cargo.lock_ 将不能使用被撤回的版本。
为了撤回一个 crate运行 `cargo yank` 并指定希望撤回的版本:

View File

@ -1,10 +1,9 @@
## 使用`Box <T>`指向堆上的数据
> [ch15-01-box.md](https://github.com/rust-lang/book/blob/master/src/ch15-01-box.md)
> <br>
> [ch15-01-box.md](https://github.com/rust-lang/book/blob/master/src/ch15-01-box.md) > <br>
> commit a203290c640a378453261948b3fee4c4c6eb3d0f
最简单直接的智能指针是 *box*,其类型是 `Box<T>`。 box 允许你将一个值放在堆上而不是栈上。留在栈上的则是指向堆数据的指针。如果你想回顾一下栈与堆的区别请参考第四章。
最简单直接的智能指针是 _box_,其类型是 `Box<T>`。 box 允许你将一个值放在堆上而不是栈上。留在栈上的则是指向堆数据的指针。如果你想回顾一下栈与堆的区别请参考第四章。
除了数据被储存在堆上而不是栈上之外box 没有性能损失。不过也没有很多额外的功能。它们多用于如下场景:
@ -12,11 +11,11 @@
- 当有大量数据并希望在确保数据不被拷贝的情况下转移所有权的时候
- 当希望拥有一个值并只关心它的类型是否实现了特定 trait 而不是其具体类型的时候
我们会在 “box 允许创建递归类型” 部分展示第一种场景。在第二种情况中,转移大量数据的所有权可能会花费很长的时间,因为数据在栈上进行了拷贝。为了改善这种情况下的性能,可以通过 box 将这些数据储存在堆上。接着,只有少量的指针数据在栈上被拷贝。第三种情况被称为 **trait 对象***trait object*),第十七章刚好有一整个部分 “为使用不同类型的值而设计的 trait 对象” 专门讲解这个主题。所以这里所学的内容会在第十七章再次用上!
我们会在 “box 允许创建递归类型” 部分展示第一种场景。在第二种情况中,转移大量数据的所有权可能会花费很长的时间,因为数据在栈上进行了拷贝。为了改善这种情况下的性能,可以通过 box 将这些数据储存在堆上。接着,只有少量的指针数据在栈上被拷贝。第三种情况被称为 **trait 对象**_trait object_),第十七章刚好有一整个部分 “为使用不同类型的值而设计的 trait 对象” 专门讲解这个主题。所以这里所学的内容会在第十七章再次用上!
### 使用 `Box<T>` 在堆上储存数据
在讨论 `Box<T>`用例之前,让我们熟悉一下语法以及如何与储存在 `Box<T>` 中的值进行交互。
在讨论 `Box<T>` 的用例之前,让我们熟悉一下语法以及如何与储存在 `Box<T>` 中的值进行交互。
示例 15-1 展示了如何使用 box 在堆上储存一个 `i32`
@ -37,15 +36,15 @@ fn main() {
### Box 允许创建递归类型
Rust 需要在编译时知道类型占用多少空间。一种无法在编译时知道大小的类型是 **递归类型***recursive type*),其值的一部分可以是相同类型的另一个值。这种值的嵌套理论上可以无限的进行下去,所以 Rust 不知道递归类型需要多少空间。不过 box 有一个已知的大小,所以通过在循环类型定义中插入 box就可以创建递归类型了。
Rust 需要在编译时知道类型占用多少空间。一种无法在编译时知道大小的类型是 **递归类型**_recursive type_),其值的一部分可以是相同类型的另一个值。这种值的嵌套理论上可以无限的进行下去,所以 Rust 不知道递归类型需要多少空间。不过 box 有一个已知的大小,所以通过在循环类型定义中插入 box就可以创建递归类型了。
让我们探索一下 *cons list*,一个函数式编程语言中的常见类型,来展示这个(递归类型)概念。除了递归之外,我们将要定义的 cons list 类型是很直白的,所以这个例子中的概念,在任何遇到更为复杂的涉及到递归类型的场景时都很实用。
让我们探索一下 _cons list_,一个函数式编程语言中的常见类型,来展示这个(递归类型)概念。除了递归之外,我们将要定义的 cons list 类型是很直白的,所以这个例子中的概念,在任何遇到更为复杂的涉及到递归类型的场景时都很实用。
#### cons list 的更多内容
*cons list* 是一个来源于 Lisp 编程语言及其方言的数据结构。在 Lisp 中,`cons` 函数“construct function" 的缩写)利用两个参数来构造一个新的列表,他们通常是一个单独的值和另一个列表。
_cons list_ 是一个来源于 Lisp 编程语言及其方言的数据结构。在 Lisp 中,`cons` 函数“construct function" 的缩写)利用两个参数来构造一个新的列表,他们通常是一个单独的值和另一个列表。
cons 函数的概念涉及到更常见的函数式编程术语;“将 *x* 与 *y* 连接” 通常意味着构建一个新的容器而将 *x* 的元素放在新容器的开头,其后则是容器 *y* 的元素。
cons 函数的概念涉及到更常见的函数式编程术语;“将 _x_ 与 _y_ 连接” 通常意味着构建一个新的容器而将 _x_ 的元素放在新容器的开头,其后则是容器 _y_ 的元素。
cons list 的每一项都包含两个元素:当前项的值和下一项。其最后一项值包含一个叫做 `Nil` 的值且没有下一项。cons list 通过递归调用 `cons` 函数产生。代表递归的终止条件base case的规范名称是 `Nil`,它宣布列表的终止。注意这不同于第六章中的 “null” 或 “nil” 的概念,他们代表无效或缺失的值。

View File

@ -1,14 +1,13 @@
## 通过 `Deref` trait 将智能指针当作常规引用处理
> [ch15-02-deref.md](https://github.com/rust-lang/book/blob/master/src/ch15-02-deref.md)
> <br>
> [ch15-02-deref.md](https://github.com/rust-lang/book/blob/master/src/ch15-02-deref.md) > <br>
> commit 44f1b71c117b0dcec7805eced0b95405167092f6
实现 `Deref` trait 允许我们重载 **解引用运算符***dereference operator*`*`(与乘法运算符或 glob 运算符相区别)。通过这种方式实现 `Deref` trait 的智能指针可以被当作常规引用来对待,可以编写操作引用的代码并用于智能指针。
实现 `Deref` trait 允许我们重载 **解引用运算符**_dereference operator_`*`(与乘法运算符或 glob 运算符相区别)。通过这种方式实现 `Deref` trait 的智能指针可以被当作常规引用来对待,可以编写操作引用的代码并用于智能指针。
让我们首先看看解引用运算符如何处理常规引用,接着尝试定义我们自己的类似 `Box<T>` 的类型并看看为何解引用运算符不能像引用一样工作。我们会探索如何实现 `Deref` trait 使得智能指针以类似引用的方式工作变为可能。最后,我们会讨论 Rust 的 **解引用强制多态***deref coercions*)功能和它是如何一同处理引用或智能指针的。
让我们首先看看解引用运算符如何处理常规引用,接着尝试定义我们自己的类似 `Box<T>` 的类型并看看为何解引用运算符不能像引用一样工作。我们会探索如何实现 `Deref` trait 使得智能指针以类似引用的方式工作变为可能。最后,我们会讨论 Rust 的 **解引用强制多态**_deref coercions_)功能和它是如何一同处理引用或智能指针的。
> 我们将要构建的 `MyBox<T>` 类型与真正的 `Box<T>` 有一个巨大的区别:我们的版本不会在堆上储存数据。这个例子重点关注 Deref`,所以其数据实际存放在何处相比其类似指针的行为来说不算重要。
> 我们将要构建的 `MyBox<T>` 类型与真正的 `Box<T>` 有一个巨大的区别:我们的版本不会在堆上储存数据。这个例子重点关注 `Deref`,所以其数据实际存放在何处相比其类似指针的行为来说不算重要。
### 通过解引用运算符追踪指针的值
@ -156,7 +155,7 @@ Rust 将 `*` 运算符替换为先调用 `deref` 方法再进行直接引用的
### 函数和方法的隐式解引用强制多态
**解引用强制多态***deref coercions*)是 Rust 表现在函数或方法传参上的一种便利。其将实现了 `Deref` 的类型的引用转换为原始类型通过 `Deref` 所能够转换的类型的引用。当这种特定类型的引用作为实参传递给和形参类型不同的函数或方法时,解引用强制多态将自动发生。这时会有一系列的 `deref` 方法被调用,把我们提供的类型转换成了参数所需的类型。
**解引用强制多态**_deref coercions_)是 Rust 表现在函数或方法传参上的一种便利。其将实现了 `Deref` 的类型的引用转换为原始类型通过 `Deref` 所能够转换的类型的引用。当这种特定类型的引用作为实参传递给和形参类型不同的函数或方法时,解引用强制多态将自动发生。这时会有一系列的 `deref` 方法被调用,把我们提供的类型转换成了参数所需的类型。
解引用强制多态的加入使得 Rust 程序员编写函数和方法调用时无需增加过多显式使用 `&``*` 的引用和解引用。这个功能也使得我们可以编写更多同时作用于引用或智能指针的代码。
@ -254,9 +253,9 @@ fn main() {
Rust 在发现类型和 trait 实现满足三种情况时会进行解引用强制多态:
*`T: Deref<Target=U>` 时从 `&T``&U`
*`T: DerefMut<Target=U>` 时从 `&mut T``&mut U`
*`T: Deref<Target=U>` 时从 `&mut T``&U`
-`T: Deref<Target=U>` 时从 `&T``&U`
-`T: DerefMut<Target=U>` 时从 `&mut T``&mut U`
-`T: Deref<Target=U>` 时从 `&mut T``&U`
头两个情况除了可变性之外是相同的:第一种情况表明如果有一个 `&T`,而 `T` 实现了返回 `U` 类型的 `Deref`,则可以直接得到 `&U`。第二种情况表明对于可变引用也有着相同的行为。

View File

@ -1,7 +1,6 @@
## 使用 `Drop` Trait 运行清理代码
> [ch15-03-drop.md](https://github.com/rust-lang/book/blob/master/src/ch15-03-drop.md)
> <br>
> [ch15-03-drop.md](https://github.com/rust-lang/book/blob/master/src/ch15-03-drop.md) > <br>
> commit 57adb83f69a69e20862d9e107b2a8bab95169b4c
对于智能指针模式来说第二个重要的 trait 是 `Drop`,其允许我们在值要离开作用域时执行一些代码。可以为任何类型提供 `Drop` trait 的实现,同时所指定的代码被用于释放类似于文件或网络连接的资源。我们在智能指针上下文中讨论 `Drop` 是因为其功能几乎总是用于实现智能指针。例如,`Box<T>` 自定义了 `Drop` 用来释放 box 所指向的堆空间。
@ -77,13 +76,13 @@ error[E0040]: explicit use of destructor method
| ^^^^ explicit destructor calls not allowed
```
错误信息表明不允许显式调用 `drop`。错误信息使用了术语 **析构函数***destructor*),这是一个清理实例的函数的通用编程概念。**析构函数** 对应创建实例的 **构造函数**。Rust 中的 `drop` 函数就是这么一个析构函数。
错误信息表明不允许显式调用 `drop`。错误信息使用了术语 **析构函数**_destructor_),这是一个清理实例的函数的通用编程概念。**析构函数** 对应创建实例的 **构造函数**。Rust 中的 `drop` 函数就是这么一个析构函数。
Rust 不允许我们显式调用 `drop` 因为 Rust 仍然会在 `main` 的结尾对值自动调用 `drop`,这会导致一个 **double free** 错误,因为 Rust 会尝试清理相同的值两次。
因为不能禁用当值离开作用域时自动插入的 `drop`,并且不能显示调用 `drop`,如果我们需要强制提早清理值,可以使用 `std::mem::drop` 函数。
`std::mem::drop` 函数不同于 `Drop` trait 中的 `drop` 方法。可以通过传递希望提早强制丢弃的值作为参数。`std::mem::drop` 位于 prelude所以我们可以修改示例 15-15 中的 `main` 来调用 `drop` 函数如示例 15-16 所示:
`std::mem::drop` 函数不同于 `Drop` trait 中的 `drop` 方法。可以通过传递希望提早强制丢弃的值作为参数。`std::mem::drop` 位于 prelude所以我们可以修改示例 15-15 中的 `main` 来调用 `drop` 函数如示例 15-16 所示:
<span class="filename">文件名: src/main.rs</span>
@ -116,9 +115,9 @@ Dropping CustomSmartPointer with data `some data`!
CustomSmartPointer dropped before the end of main.
```
```Dropping CustomSmartPointer with data `some data`!``` 出现在 `CustomSmartPointer created.``CustomSmartPointer dropped before the end of main.` 之间,表明了 `drop` 方法被调用了并在此丢弃了 `c`
`` Dropping CustomSmartPointer with data `some data`! `` 出现在 `CustomSmartPointer created.``CustomSmartPointer dropped before the end of main.` 之间,表明了 `drop` 方法被调用了并在此丢弃了 `c`
`Drop` trait 实现中指定的代码可以用于许多方面来使得清理变得方便和安全:比如可以用其创建我们自己的内存分配器!通过 `Drop` trait 和 Rust 所有权系统,你无需担心之后清理代码Rust 会自动考虑这些问题。
`Drop` trait 实现中指定的代码可以用于许多方面来使得清理变得方便和安全:比如可以用其创建我们自己的内存分配器!通过 `Drop` trait 和 Rust 所有权系统,你无需担心之后的代码清理Rust 会自动考虑这些问题。
我们也无需担心意外的清理掉仍在使用的值,这会造成编译器错误:所有权系统确保引用总是有效的,也会确保 `drop` 只会在值不再被使用时被调用一次。

View File

@ -1,16 +1,15 @@
## `Rc<T>` 引用计数智能指针
> [ch15-04-rc.md](https://github.com/rust-lang/book/blob/master/src/ch15-04-rc.md)
> <br>
> [ch15-04-rc.md](https://github.com/rust-lang/book/blob/master/src/ch15-04-rc.md) > <br>
> commit 6f292c8439927b4c5b870dd4afd2bfc52cc4eccc
大部分情况下所有权是非常明确的:可以准确知道哪个变量拥有某个值。然而,有些情况单个值可能会有多个所有者。例如,在图数据结构中,多个边可能指向相同的结点,而这个结点从概念上讲为所有指向它的边所拥有。结点直到没有任何边指向它之前都不应该被清理。
大部分情况下所有权是非常明确的:可以准确知道哪个变量拥有某个值。然而,有些情况单个值可能会有多个所有者。例如,在图数据结构中,多个边可能指向相同的结点,而这个结点从概念上讲为所有指向它的边所拥有。结点直到没有任何边指向它之前都不应该被清理。
为了启用多所有权Rust 有一个叫做 `Rc<T>` 的类型。其名称为 **引用计数***reference counting*)的缩写。引用计数意味着记录一个值引用的数量来知晓这个值是否仍在被使用。如果某个值有零个引用,就代表没有任何有效引用并可以被清理。
为了启用多所有权Rust 有一个叫做 `Rc<T>` 的类型。其名称为 **引用计数**_reference counting_)的缩写。引用计数意味着记录一个值引用的数量来知晓这个值是否仍在被使用。如果某个值有零个引用,就代表没有任何有效引用并可以被清理。
可以将其想象为客厅中的电视。当一个人进来看电视时,他打开电视。其他人也可以进来看电视。当最后一个人离开房间时,他关掉电视因为它不再被使用了。如果某人在其他人还在看的时候就关掉了电视,正在看电视的人肯定会抓狂的!
`Rc<T>` 用于当我们希望在堆上分配一些内存供程序的多个部分读取,而且无法在编译时确定程序的一部分会最后结束使用它的时候。如果确实知道哪部分是最后一个结束使用的话,就可以令其成为数据的所有者同时正常的所有权规则就可以在编译时生效。
`Rc<T>` 用于当我们希望在堆上分配一些内存供程序的多个部分读取,而且无法在编译时确定程序的一部分会最后结束使用它的时候。如果确实知道哪部分是最后一个结束使用的话,就可以令其成为数据的所有者同时正常的所有权规则就可以在编译时生效。
注意 `Rc<T>` 只能用于单线程场景;第十六章并发会涉及到如何在多线程程序中进行引用计数。
@ -88,7 +87,7 @@ fn main() {
<span class="caption">示例 15-18: 使用 `Rc<T>` 定义的 `List`</span>
需要使用 `use` 语句将 `Rc<T>` 引入作用域因为它不在 prelude 中。在 `main` 中创建了存放 5 和 10 的列表并将其存放在 `a` 的新的 `Rc<List>` 中。接着当创建 `b``c` 时,调用 `Rc::clone` 函数并传递 `a``Rc<List>` 的引用作为参数。
需要使用 `use` 语句将 `Rc<T>` 引入作用域因为它不在 prelude 中。在 `main` 中创建了存放 5 和 10 的列表并将其存放在 `a` 的新的 `Rc<List>` 中。接着当创建 `b``c` 时,调用 `Rc::clone` 函数并传递 `a``Rc<List>` 的引用作为参数。
也可以调用 `a.clone()` 而不是 `Rc::clone(&a)`,不过在这里 Rust 的习惯是使用 `Rc::clone`。`Rc::clone` 的实现并不像大部分类型的 `clone` 实现那样对所有数据进行深拷贝。`Rc::clone` 只会增加引用计数,这并不会花费多少时间。深拷贝可能会花费很长时间。通过使用 `Rc::clone` 进行引用计数,可以明显的区别深拷贝类的克隆和增加引用计数类的克隆。当查找代码中的性能问题时,只需考虑深拷贝类的克隆而无需考虑 `Rc::clone` 调用。
@ -124,7 +123,7 @@ fn main() {
<span class="caption">示例 15-19打印出引用计数</span>
在程序中每个引用计数变化的点,会打印出引用计数,其值可以通过调用 `Rc::strong_count` 函数获得。这个函数叫做 `strong_count` 而不是 `count` 是因为 `Rc<T>` 也有 `weak_count`;在 [“避免引用循环:将 `Rc<T>` 变为 `Weak<T>`”][preventing-ref-cycles] 部分会讲解 `weak_count` 的用途。
在程序中每个引用计数变化的点,会打印出引用计数,其值可以通过调用 `Rc::strong_count` 函数获得。这个函数叫做 `strong_count` 而不是 `count` 是因为 `Rc<T>` 也有 `weak_count`;在 [“避免引用循环:将 `Rc<T>` 变为 `Weak<T>`”](preventing-ref-cycles) 部分会讲解 `weak_count` 的用途。
这段代码会打印出:
@ -141,4 +140,4 @@ count after c goes out of scope = 2
通过不可变引用, `Rc<T>` 允许在程序的多个部分之间只读地共享数据。如果 `Rc<T>` 也允许多个可变引用,则会违反第四章讨论的借用规则之一:相同位置的多个可变借用可能造成数据竞争和不一致。不过可以修改数据是非常有用的!在下一部分,我们将讨论内部可变性模式和 `RefCell<T>` 类型,它可以与 `Rc<T>` 结合使用来处理不可变性的限制。
[preventing-ref-cycles]: ch15-06-reference-cycles.html#preventing-reference-cycles-turning-an-rct-into-a-weakt
[preventing-ref-cycles]: ch15-06-reference-cycles.html#preventing-reference-cycles-turning-an-rct-into-a-weakt

View File

@ -1,14 +1,13 @@
## `RefCell<T>` 和内部可变性模式
> [ch15-05-interior-mutability.md](https://github.com/rust-lang/book/blob/master/src/ch15-05-interior-mutability.md)
> <br>
> [ch15-05-interior-mutability.md](https://github.com/rust-lang/book/blob/master/src/ch15-05-interior-mutability.md) > <br>
> commit 26565efc3f62d9dacb7c2c6d0f5974360e459493
**内部可变性***Interior mutability*)是 Rust 中的一个设计模式,它允许你即使在有不可变引用时改变数据,这通常是借用规则所不允许的。为了改变数据,该模式在数据结构中使用 `unsafe` 代码来模糊 Rust 通常的可变性和借用规则。我们还未讲到不安全代码;第十九章会学习它们。当可以确保代码在运行时会遵守借用规则,即使编译器不能保证的情况,可以选择使用那些运用内部可变性模式的类型。所涉及的 `unsafe` 代码将被封装进安全的 API 中,而外部类型仍然是不可变的。
**内部可变性**_Interior mutability_)是 Rust 中的一个设计模式,它允许你即使在有不可变引用时改变数据,这通常是借用规则所不允许的。为了改变数据,该模式在数据结构中使用 `unsafe` 代码来模糊 Rust 通常的可变性和借用规则。我们还未讲到不安全代码;第十九章会学习它们。当可以确保代码在运行时会遵守借用规则,即使编译器不能保证的情况,可以选择使用那些运用内部可变性模式的类型。所涉及的 `unsafe` 代码将被封装进安全的 API 中,而外部类型仍然是不可变的。
让我们通过遵循内部可变性模式的 `RefCell<T>` 类型来开始探索。
### 通过 `RefCell<T>` 在运行时检查借用规则
### 通过 `RefCell<T>` 在运行时检查借用规则
不同于 `Rc<T>``RefCell<T>` 代表其数据的唯一的所有权。那么是什么让 `RefCell<T>` 不同于像 `Box<T>` 这样的类型呢?回忆一下第四章所学的借用规则:
@ -19,7 +18,7 @@
在编译时检查借用规则的优势是这些错误将在开发过程的早期被捕获同时对没有运行时性能影响,因为所有的分析都提前完成了。为此,在编译时检查借用规则是大部分情况的最佳选择,这也正是其为何是 Rust 的默认行为。
相反在运行时检查借用规则的好处则是允许出现特定内存安全的场景,而它们在编译时检查中是不允许的。静态分析,正如 Rust 编译器,是天生保守的。代码的一些属性不可能通过分析代码发现:其中最著名的就是 [停机问题Halting Problem](https://zh.wikipedia.org/wiki/%E5%81%9C%E6%9C%BA%E9%97%AE%E9%A2%98),这超出了本书的范畴,不过如果你感兴趣的话这是一个值得研究的有趣主题。
相反在运行时检查借用规则的好处则是允许出现特定内存安全的场景,而它们在编译时检查中是不允许的。静态分析,正如 Rust 编译器,是天生保守的。代码的一些属性不可能通过分析代码发现:其中最著名的就是 [停机问题Halting Problem](https://zh.wikipedia.org/wiki/%E5%81%9C%E6%9C%BA%E9%97%AE%E9%A2%98),这超出了本书的范畴,不过如果你感兴趣的话这是一个值得研究的有趣主题。
因为一些分析是不可能的,如果 Rust 编译器不能通过所有权规则编译,它可能会拒绝一个正确的程序;从这种角度考虑它是保守的。如果 Rust 接受不正确的程序,那么用户也就不会相信 Rust 所做的保证了。然而,如果 Rust 拒绝正确的程序,虽然会给程序员带来不便,但不会带来灾难。`RefCell<T>` 正是用于当你确信代码遵守借用规则,而编译器不能理解和确定的时候。
@ -27,15 +26,15 @@
如下为选择 `Box<T>``Rc<T>` 或 `RefCell<T>` 的理由:
* `Rc<T>` 允许相同数据有多个所有者;`Box<T>` 和 `RefCell<T>` 有单一所有者。
* `Box<T>` 允许在编译时执行不可变或可变借用检查;`Rc<T>`仅允许在编译时执行不可变借用检查;`RefCell<T>` 允许在运行时执行不可变或可变借用检查。
* 因为 `RefCell<T>` 允许在运行时执行可变借用检查,所以我们可以在即便 `RefCell<T>` 自身是不可变的情况下修改其内部的值。
- `Rc<T>` 允许相同数据有多个所有者;`Box<T>` 和 `RefCell<T>` 有单一所有者。
- `Box<T>` 允许在编译时执行不可变或可变借用检查;`Rc<T>`仅允许在编译时执行不可变借用检查;`RefCell<T>` 允许在运行时执行不可变或可变借用检查。
- 因为 `RefCell<T>` 允许在运行时执行可变借用检查,所以我们可以在即便 `RefCell<T>` 自身是不可变的情况下修改其内部的值。
在不可变值内部改变值就是 **内部可变性** 模式。让我们看看何时内部可变性是有用的,并讨论这是如何成为可能的。
### 内部可变性:不可变值的可变借用
借用规则的一个推论是当有一个不可变值时,不能可变借用它。例如,如下代码不能编译:
借用规则的一个推论是当有一个不可变值时,不能可变借用它。例如,如下代码不能编译:
```rust,ignore,does_not_compile
fn main() {
@ -56,13 +55,13 @@ error[E0596]: cannot borrow immutable local variable `x` as mutable
| ^ cannot borrow mutably
```
然而,特定情况下在值的方法内部能够修改自身是很有用的;而不是在其他代码中,此时值仍然是不可变。值方法外部的代码不能修改其值。`RefCell<T>` 是一个获得内部可变性的方法。`RefCell<T>` 并没有完全绕开借用规则,编译器中的借用检查器允许内部可变性并相应在运行时检查借用规则。如果违反了这些规则,会得到 `panic!` 而不是编译错误。
然而,特定情况下在值的方法内部能够修改自身是很有用的,而不是在其他代码中。此时值仍然是不可变的,值方法外部的代码不能修改其值。`RefCell<T>` 是一个获得内部可变性的方法。`RefCell<T>` 并没有完全绕开借用规则,编译器中的借用检查器允许内部可变性并相应在运行时检查借用规则。如果违反了这些规则,会得到 `panic!` 而不是编译错误。
让我们通过一个实际的例子来探索何处可以使用 `RefCell<T>` 来修改不可变值并看看为何这么做是有意义的。
#### 内部可变性的用例mock 对象
**测试替身***test double*)是一个通用编程概念,它代表一个在测试中替代某个类型的类型。**mock 对象** 是特定类型的测试替身,它们记录测试过程中发生了什么以便可以断言操作是正确的。
**测试替身**_test double_)是一个通用编程概念,它代表一个在测试中替代某个类型的类型。**mock 对象** 是特定类型的测试替身,它们记录测试过程中发生了什么以便可以断言操作是正确的。
虽然 Rust 没有与其他语言中的对象完全相同的对象Rust 也没有像其他语言那样在标准库中内建 mock 对象功能,不过我们确实可以创建一个与 mock 对象有着相同功能的结构体。
@ -168,9 +167,9 @@ error[E0596]: cannot borrow immutable field `self.sent_messages` as mutable
| ^^^^^^^^^^^^^^^^^^ cannot mutably borrow immutable field
```
不能修改 `MockMessenger` 来记录消息,因为 `send` 方法获取 `self` 的不可变引用。我们也不能参考错误文本的建议使用 `&mut self` 替代,因为这样 `send` 的签名就不符合 `Messenger` trait 定义中的签名了(请随意尝试如此修改并看看会出现什么错误信息)。
不能修改 `MockMessenger` 来记录消息,因为 `send` 方法获取 `self` 的不可变引用。我们也不能参考错误文本的建议使用 `&mut self` 替代,因为这样 `send` 的签名就不符合 `Messenger` trait 定义中的签名了(可以试着这么改,看看会出现什么错误信息)。
这正是内部可变性的用武之地!我们将通过 `RefCell` 来储存 `sent_messages`,然 `send` 将能够修改 `sent_messages` 并储存消息。示例 15-22 展示了代码:
这正是内部可变性的用武之地!我们将通过 `RefCell` 来储存 `sent_messages`,然 `send` 将能够修改 `sent_messages` 并储存消息。示例 15-22 展示了代码:
<span class="filename">文件名: src/lib.rs</span>
@ -256,7 +255,7 @@ mod tests {
### `RefCell<T>` 在运行时记录借用
当创建不可变和可变引用时,我们分别使用 `&``&mut` 语法。对于 `RefCell<T>` 来说,则是 `borrow``borrow_mut` 方法,这属于 `RefCell<T>` 安全 API 的一部分。`borrow` 方法返回 `Ref` 类型的智能指针,`borrow_mut` 方法返回 `RefMut` 类型的智能指针。这两个类型都实现了 `Deref` 所以可以当作常规引用对待。
当创建不可变和可变引用时,我们分别使用 `&``&mut` 语法。对于 `RefCell<T>` 来说,则是 `borrow``borrow_mut` 方法,这属于 `RefCell<T>` 安全 API 的一部分。`borrow` 方法返回 `Ref` 类型的智能指针,`borrow_mut` 方法返回 `RefMut` 类型的智能指针。这两个类型都实现了 `Deref`所以可以当作常规引用对待。
`RefCell<T>` 记录当前有多少个活动的 `Ref<T>``RefMut<T>` 智能指针。每次调用 `borrow``RefCell<T>` 将活动的不可变借用计数加一。当 `Ref` 值离开作用域时,不可变借用计数减一。就像编译时借用规则一样,`RefCell<T>` 在任何时候只允许有多个不可变借用或一个可变借用。
@ -289,7 +288,7 @@ note: Run with `RUST_BACKTRACE=1` for a backtrace.
注意代码 panic 和信息 `already borrowed: BorrowMutError`。这也就是 `RefCell<T>` 如何在运行时处理违反借用规则的情况。
在运行时捕获借用错误而不是编译时意味着将会在开发过程的后期才会发现错误,甚至有可能发布到生产环境才发现还会因为在运行时而不是编译时记录借用而导致少量的运行时性能惩罚。然而,使用 `RefCell` 使得在只允许不可变值的上下文中编写修改自身以记录消息的 mock 对象成为可能。虽然有取舍,但是我们可以选择使用 `RefCell<T>` 来获得比常规引用所能提供的更多的功能。
在运行时捕获借用错误而不是编译时意味着将会在开发过程的后期才会发现错误,甚至有可能发布到生产环境才发现还会因为在运行时而不是编译时记录借用而导致少量的运行时性能惩罚。然而,使用 `RefCell` 使得在只允许不可变值的上下文中编写修改自身以记录消息的 mock 对象成为可能。虽然有取舍,但是我们可以选择使用 `RefCell<T>` 来获得比常规引用所能提供的更多的功能。
### 结合 `Rc<T>``RefCell<T>` 来拥有多个可变数据所有者
@ -328,7 +327,7 @@ fn main() {
<span class="caption">示例 15-24使用 `Rc<RefCell<i32>>` 创建可以修改的 `List`</span>
这里创建了一个 `Rc<RefCell<i32>` 实例并储存在变量 `value` 中以便之后直接访问。接着在 `a` 中用包含 `value``Cons` 成员创建了一个 `List`。需要克隆 `value` 以便 `a``value` 都能拥有其内部值 `5` 的所有权,而不是将所有权从 `value` 移动到 `a` 或者让 `a` 借用 `value`
这里创建了一个 `Rc<RefCell<i32>>` 实例并储存在变量 `value` 中以便之后直接访问。接着在 `a` 中用包含 `value``Cons` 成员创建了一个 `List`。需要克隆 `value` 以便 `a``value` 都能拥有其内部值 `5` 的所有权,而不是将所有权从 `value` 移动到 `a` 或者让 `a` 借用 `value`
我们将列表 `a` 封装进了 `Rc<T>` 这样当创建列表 `b``c` 时,他们都可以引用 `a`,正如示例 15-18 一样。
@ -342,8 +341,8 @@ b after = Cons(RefCell { value: 6 }, Cons(RefCell { value: 15 }, Nil))
c after = Cons(RefCell { value: 10 }, Cons(RefCell { value: 15 }, Nil))
```
这是非常巧妙的!通过使用 `RefCell<T>`,我们可以拥有一个表面上不可变的 `List`,不过可以使用 `RefCell<T>` 中提供内部可变性的方法来在需要时修改数据。`RefCell<T>` 的运行时借用规则检查也确实保护我们免于出现数据竞争,而且我们也决定牺牲一些速度来换取数据结构的灵活性
这是非常巧妙的!通过使用 `RefCell<T>`,我们可以拥有一个表面上不可变的 `List`,不过可以使用 `RefCell<T>` 中提供内部可变性的方法来在需要时修改数据。`RefCell<T>` 的运行时借用规则检查也确实保护我们免于出现数据竞争——有时为了数据结构的灵活性而付出一些性能是值得的
标准库中也有其他提供内部可变性的类型,比如 `Cell<T>`,它有些类似`RefCell<T>`)除了相比提供内部值的引用,其值被拷贝进和拷贝出 `Cell<T>`。还有 `Mutex<T>`,其提供线程间安全的内部可变性,下一章并发会讨论它的应用。请查看标准库来获取更多细节和不同类型之间的区别。
标准库中也有其他提供内部可变性的类型,比如 `Cell<T>`,它有些类似 `RefCell<T>`,除了提供内部值的引用,其值还会被拷贝进和拷贝出 `Cell<T>`。还有 `Mutex<T>`,其提供线程间安全的内部可变性,我们将在第 16 章中讨论其用法。请查看标准库来获取更多细节和不同类型之间的区别。
[wheres-the---operator]: ch05-03-method-syntax.html#wheres-the---operator
[wheres-the---operator]: ch05-03-method-syntax.html#wheres-the---operator

View File

@ -1,10 +1,9 @@
## 引用循环与内存泄漏
> [ch15-06-reference-cycles.md](https://github.com/rust-lang/book/blob/master/src/ch15-06-reference-cycles.md)
> <br>
> [ch15-06-reference-cycles.md](https://github.com/rust-lang/book/blob/master/src/ch15-06-reference-cycles.md) > <br>
> commit f617d58c1a88dd2912739a041fd4725d127bf9fb
Rust 的内存安全保证使其难以意外地制造永远也不会被清理的内存(被称为 **内存泄露***memory leak*)),但并不是不可能。与在编译时拒绝数据竞争不同, Rust 并不保证完全地避免内存泄露,这意味着内存泄露在 Rust 被认为是内存安全的。这一点可以通过 `Rc<T>``RefCell<T>` 看出:有可能会创建个个项之间相互引用的引用。这会造成内存泄露,因为每一项的引用计数永远也到不了 0其值也永远也不会被丢弃。
Rust 的内存安全保证使其难以意外地制造永远也不会被清理的内存(被称为 **内存泄露**_memory leak_)),但并不是不可能。与在编译时拒绝数据竞争不同, Rust 并不保证完全地避免内存泄露,这意味着内存泄露在 Rust 被认为是内存安全的。这一点可以通过 `Rc<T>``RefCell<T>` 看出:创建引用循环的可能性是存在的。这会造成内存泄露,因为每一项的引用计数永远也到不了 0其值也永远也不会被丢弃。
### 制造引用循环
@ -118,21 +117,21 @@ a rc count after changing a = 2
创建引用循环并不容易,但也不是不可能。如果你有包含 `Rc<T>``RefCell<T>` 值或类似的嵌套结合了内部可变性和引用计数的类型,请务必小心确保你没有形成一个引用循环;你无法指望 Rust 帮你捕获它们。创建引用循环是一个程序上的逻辑 bug你应该使用自动化测试、代码评审和其他软件开发最佳实践来使其最小化。
另一个解决方案是重新组织数据结构使得一些引用有所有权而另一些则没有。如此,循环将由一些有所有权的关系和一些没有所有权的关系,而只有所有权关系才影响值是否被丢弃。在示例 15-25 中,我们总是希望 `Cons` 成员拥有其列表,所以重新组织数据结构是不可能的。让我们看看一个由父结点和子结点构成的图的例子,观察何时无所有权关系是一个好的避免引用循环的方法
另一个解决方案是重新组织数据结构,使得一部分引用拥有所有权而另一部分没有。换句话说,循环将由一些拥有所有权的关系和一些无所有权的关系组成,只有所有权关系才能影响值是否可以被丢弃。在示例 15-25 中,我们总是希望 `Cons` 成员拥有其列表,所以重新组织数据结构是不可能的。让我们看看一个由父结点和子结点构成的图的例子,观察何时是使用无所有权的关系来避免引用循环的合适时机
### 避免引用循环:将 `Rc<T>` 变为 `Weak<T>`
到目前为止,我们已经展示了调用 `Rc::clone` 会增加 `Rc<T>` 实例的 `strong_count`,和 `Rc<T>` 实例只在其 `strong_count` 为 0 时才会被清理。也可以通过调用 `Rc::downgrade` 并传递 `Rc` 实例的引用来创建其值的 **弱引用***weak reference*)。调用 `Rc::downgrade` 时会得到 `Weak<T>` 类型的智能指针。不同于将 `Rc<T>` 实例的 `strong_count` 加一,调用 `Rc::downgrade` 会将 `weak_count` 加一。`Rc<T>` 类型使用 `weak_count` 来记录其存在多少个 `Weak<T>` 引用,类似于 `strong_count`。其区别在于 `weak_count` 无需计数为 0 就能使 `Rc` 实例被清理。
到目前为止,我们已经展示了调用 `Rc::clone` 会增加 `Rc<T>` 实例的 `strong_count`,和只在其 `strong_count` 为 0 时才会被清理`Rc<T>` 实例也可以通过调用 `Rc::downgrade` 并传递 `Rc` 实例的引用来创建其值的 **弱引用**_weak reference_)。调用 `Rc::downgrade` 时会得到 `Weak<T>` 类型的智能指针。不同于将 `Rc<T>` 实例的 `strong_count` 加一,调用 `Rc::downgrade` 会将 `weak_count` 加一。`Rc<T>` 类型使用 `weak_count` 来记录其存在多少个 `Weak<T>` 引用,类似于 `strong_count`。其区别在于 `weak_count` 无需计数为 0 就能使 `Rc` 实例被清理。
强引用代表如何共享 `Rc<T>` 实例的所有权。弱引用并不代表所有权关系。他们不会造成引用循环,因为任何引入了弱引用的循环一旦所涉及的强引用计数为 0 就会被打破
强引用代表如何共享 `Rc<T>` 实例的所有权,但弱引用并不属于所有权关系。他们不会造成引用循环,因为任何弱引用的循环会在其相关的强引用计数为 0 时被打断
因为 `Weak<T>` 引用的值可能已经被丢弃了,为了使用 `Weak<T>` 所指向的值,我们必须确保其值仍然有效。为此可以调用 `Weak<T>` 实例的 `upgrade` 方法,这会返回 `Option<Rc<T>>`。如果 `Rc<T>` 值还未被丢弃则结果是 `Some`,如果 `Rc<T>` 已经被丢弃则结果是 `None`。因为 `upgrade` 返回一个 `Option<T>`,我们确信 Rust 会处理 `Some``None`的情况,并且不会有一个无效的指针。
因为 `Weak<T>` 引用的值可能已经被丢弃了,为了使用 `Weak<T>` 所指向的值,我们必须确保其值仍然有效。为此可以调用 `Weak<T>` 实例的 `upgrade` 方法,这会返回 `Option<Rc<T>>`。如果 `Rc<T>` 值还未被丢弃,则结果是 `Some`;如果 `Rc<T>` 已被丢弃,则结果是 `None`。因为 `upgrade` 返回一个 `Option<T>`,我们确信 Rust 会处理 `Some``None` 的情况,所以它不会返回非法指针。
作为一个例子,不同于使用一个某项只知道其下一项的列表,我们会创建一个某项知道其子项 **和** 父项的树形结构。
我们会创建一个某项知道其子项**和**父项的树形结构的例子,而不是只知道其下一项的列表
#### 创建树形数据结构:带有子结点的 `Node`
让我们从一个叫做 `Node` 的存放拥有所有权的 `i32` 值和其子 `Node` 值引用的结构体开始
在最开始,我们将会构建一个带有子节点的树。让我们创建一个用于存放其拥有所有权的 `i32` 值和其子节点引用的 `Node`
<span class="filename">文件名: src/main.rs</span>
@ -147,7 +146,7 @@ struct Node {
}
```
我们希望能够 `Node` 拥有其子结点,同时也希望通过变量来共享所有权,以便可以直接访问树中的每一个 `Node`为此 `Vec<T>` 的项的类型被定义为 `Rc<Node>`。我们还希望能改其他结点的子结点,所以 `children``Vec<Rc<Node>>` 被放进了 `RefCell<T>`
我们希望能够 `Node` 拥有其子结点,同时也希望通过变量来共享所有权,以便可以直接访问树中的每一个 `Node`为此 `Vec<T>` 的项的类型被定义为 `Rc<Node>`。我们还希望能改其他结点的子结点,所以 `children``Vec<Rc<Node>>` 被放进了 `RefCell<T>`
接下来,使用此结构体定义来创建一个叫做 `leaf` 的带有值 3 且没有子结点的 `Node` 实例,和另一个带有值 5 并以 `leaf` 作为子结点的实例 `branch`,如示例 15-27 所示:

View File

@ -1,12 +1,11 @@
# 无畏并发
> [ch16-00-concurrency.md](https://github.com/rust-lang/book/blob/master/src/ch16-00-concurrency.md)
> <br>
> [ch16-00-concurrency.md](https://github.com/rust-lang/book/blob/master/src/ch16-00-concurrency.md) > <br>
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
安全并高效的处理并发编程是 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。
> 注意:出于简洁的考虑,我们将很多问题归类为 **并发**,而不是更准确的区分 **并发和(或)并行**。如果这是一本专注于并发和/或并行的书,我们肯定会更加精确的。对于本章,当我们谈到 **并发** 时,请自行脑内替换为 **并发和(或)并行**
@ -14,7 +13,7 @@
如下是本章将要涉及到的内容:
* 如何创建线程来同时运行多段代码。
* **消息传递***Message passing*并发其中通道channel被用来在线程间传递消息。
* **共享状态***Shared state*)并发,其中多个线程可以访问同一片数据。
* `Sync``Send` trait他们允许 Rust 的并发保证能被扩展到用户定义的和标准库中提供的类型中。
- 如何创建线程来同时运行多段代码。
- **消息传递**_Message passing_并发其中通道channel被用来在线程间传递消息。
- **共享状态**_Shared state_)并发,其中多个线程可以访问同一片数据。
- `Sync``Send` trait他们允许 Rust 的并发保证能被扩展到用户定义的和标准库中提供的类型中。

View File

@ -1,34 +1,33 @@
## 使用线程同时运行代码
> [ch16-01-threads.md](https://github.com/rust-lang/book/blob/master/src/ch16-01-threads.md)
> <br>
> [ch16-01-threads.md](https://github.com/rust-lang/book/blob/master/src/ch16-01-threads.md) > <br>
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
在大部分现代操作系统中,执行中程序的代码运行于一个 **进程***process*)中,操作系统则负责管理多个进程。在程序内部,也可以拥有多个同时运行的独立部分。这个运行这些独立部分的功能被称为 **线程***threads*)。
在大部分现代操作系统中,执行中程序的代码运行于一个 **进程**_process_)中,操作系统则负责管理多个进程。在程序内部,也可以拥有多个同时运行的独立部分。这个运行这些独立部分的功能被称为 **线程**_threads_)。
将程序中的计算拆分进多个线程可以改善性能,因为程序可以同时进行多个任务,不过这也会增加复杂性。因为线程是同时运行的,所以无法预先保证不同线程中的代码的执行顺序。这会导致诸如此类的问题:
* 竞争状态Race conditions多个线程以不一致的顺序访问数据或资源
* 死锁Deadlocks两个线程相互等待对方停止使用其所拥有的资源这会阻止它们继续运行
* 只会发生在特定情况且难以稳定重现和修复的 bug
- 竞争状态Race conditions多个线程以不一致的顺序访问数据或资源
- 死锁Deadlocks两个线程相互等待对方停止使用其所拥有的资源这会阻止它们继续运行
- 只会发生在特定情况且难以稳定重现和修复的 bug
Rust 尝试缓和使用线程的负面影响。不过在多线程上下文中编程仍需格外小心,同时其所要求的代码结构也不同于运行于单线程的程序。
编程语言有一些不同的方法来实现线程。很多操作系统提供了创建新线程的 API。这种由编程语言调用操作系统 API 创建线程的模模型有时被称为 *1:1*,一个 OS 线程对应一个语言线程。
编程语言有一些不同的方法来实现线程。很多操作系统提供了创建新线程的 API。这种由编程语言调用操作系统 API 创建线程的模型有时被称为 _1:1_,一个 OS 线程对应一个语言线程。
很多编程语言提供了自己特殊的线程实现。编程语言提供的线程被称为 **绿色***green*)线程,使用绿色线程的语言会在不同数量的 OS 线程的上下文中执行它们。为此,绿色线程模式被称为 *M:N* 模型:`M` 个绿色线程对应 `N` 个 OS 线程,这里 `M``N` 不必相同。
很多编程语言提供了自己特殊的线程实现。编程语言提供的线程被称为 **绿色**_green_)线程,使用绿色线程的语言会在不同数量的 OS 线程的上下文中执行它们。为此,绿色线程模式被称为 _M:N_ 模型:`M` 个绿色线程对应 `N` 个 OS 线程,这里 `M``N` 不必相同。
每一个模型都有其优势和取舍。对于 Rust 来说最重要的取舍是运行时支持。**运行时***Runtime*)是一个令人迷惑的概念,其在不同上下文中可能有不同的含义。
每一个模型都有其优势和取舍。对于 Rust 来说最重要的取舍是运行时支持。**运行时**_Runtime_)是一个令人迷惑的概念,其在不同上下文中可能有不同的含义。
在当前上下文中,**运行时** 代表二进制文件中包含的由语言自身提供的代码。这些代码根据语言的不同可大可小,不过任何非汇编语言都会有一定数量的运行时代码。为此,通常人们说一个语言 “没有运行时”,一般意味着 “小运行时”。更小的运行时拥有更少的功能不过其优势在于更小的二进制输出,这使其易于在更多上下文中与其他语言相结合。虽然很多语言觉得增加运行时来换取更多功能没有什么问题,但是 Rust 需要做到几乎没有运行时,同时为了保持高性能必需能够调用 C 语言,这点也是不能妥协的。
绿色线程的 M:N 模型需要更大的语言运行时来管理这些线程。为此Rust 标准库只提供了 1:1 线程模型实现。因为 Rust 是足够底层的语言,有一些 crate 实现了 M:N 线程模型,如果你宁愿牺牲性能来换取例如更好的线程运行控制和更低的上下文切换成本
绿色线程的 M:N 模型需要更大的语言运行时来管理这些线程。因此Rust 标准库只提供了 1:1 线程模型实现。由于 Rust 是较为底层的语言,如果你愿意牺牲性能来换取的抽象,以获得对线程运行更精细的控制及更低的上下文切换成本,你可以使用实现了 M:N 线程模型的 crate
现在我们明白了 Rust 中的线程是如何定义的,让我们开始探索如何使用标准库提供的线程相关的 API 吧。
### 使用 `spawn` 创建新线程
为了创建一个新线程,需要调用 `thread::spawn` 函数并传递一个闭包(第十三章学习了闭包),其包含希望在新线程运行的代码。示例 16-1 中的例子在主线程打印了一些文本而另一些文本则由新线程打印:
为了创建一个新线程,需要调用 `thread::spawn` 函数并传递一个闭包(第十三章学习了闭包),并在包含希望在新线程运行的代码。示例 16-1 中的例子在主线程打印了一些文本而另一些文本则由新线程打印:
<span class="filename">文件名: src/main.rs</span>
@ -67,7 +66,7 @@ hi number 4 from the spawned thread!
hi number 5 from the spawned thread!
```
`thread::sleep` 调用强制线程停止执行一小段时间,这会允许其他不同的线程运行。这些线程可能会轮流运行,不过并不保证如此:这依赖操作系统如何调度线程。在这里,主线程首先打印,即便新创建线程的打印语句位于程序的开头甚至即便我们告诉新建的线程打印直到 `i` 等于 9 ,它在主线程结束之前也只打印到了 5。
`thread::sleep` 调用强制线程停止执行一小段时间,这会允许其他不同的线程运行。这些线程可能会轮流运行,不过并不保证如此:这依赖操作系统如何调度线程。在这里,主线程首先打印,即便新创建线程的打印语句位于程序的开头甚至即便我们告诉新建的线程打印直到 `i` 等于 9 ,它在主线程结束之前也只打印到了 5。
如果运行代码只看到了主线程的输出,或没有出现重叠打印的现象,尝试增加 range 的数值来增加操作系统切换线程的机会。
@ -102,7 +101,7 @@ fn main() {
<span class="caption">示例 16-2: 从 `thread::spawn` 保存一个 `JoinHandle` 以确保该线程能够运行至结束</span>
通过调用 handle `join` 会阻塞当前线程直到 handle 所代表的线程结束。**阻塞***Blocking* 线程意味着阻止该线程执行工作或退出。因为我们将 `join` 调用放在了主线程的 `for` 循环之后,运行示例 16-2 应该会产生类似这样的输出:
通过调用 handle 的 `join` 会阻塞当前线程直到 handle 所代表的线程结束。**阻塞**_Blocking_ 线程意味着阻止该线程执行工作或退出。因为我们将 `join` 调用放在了主线程的 `for` 循环之后,运行示例 16-2 应该会产生类似这样的输出:
```text
hi number 1 from the main thread!

View File

@ -1,15 +1,14 @@
## 使用消息传递在线程间传送数据
> [ch16-02-message-passing.md](https://github.com/rust-lang/book/blob/master/src/ch16-02-message-passing.md)
> <br>
> [ch16-02-message-passing.md](https://github.com/rust-lang/book/blob/master/src/ch16-02-message-passing.md) > <br>
> commit 26565efc3f62d9dacb7c2c6d0f5974360e459493
一个日益流行的确保安全并发的方式是 **消息传递***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.”)
Rust 中一个实现消息传递并发的主要工具是 **通道***channel*),一个 Rust 标准库提供了其实现的编程概念。你可以将其想象为一个水流的通道,比如河流或小溪。如果你将诸如橡皮鸭或小船之类的东西放入其中,它们会顺流而下到达下游。
Rust 中一个实现消息传递并发的主要工具是 **通道**_channel_),一个 Rust 标准库提供了其实现的编程概念。你可以将其想象为一个水流的通道,比如河流或小溪。如果你将诸如橡皮鸭或小船之类的东西放入其中,它们会顺流而下到达下游。
编程中的通道有两部分组成一个发送者transmitter和一个接收者receiver。发送者一端位于上游位置在这里可以将橡皮鸭放入河中接收者部分则位于下游橡皮鸭最终会漂流至此。代码中的一部分调用发送者的方法以及希望发送的数据另一部分则检查接收端收到到达的消息。当发送者或接收者任一被丢弃时可以认为通道被 **关闭***closed*)了
编程中的通道有两部分组成一个发送者transmitter和一个接收者receiver。发送者一端位于上游位置在这里可以将橡皮鸭放入河中接收者部分则位于下游橡皮鸭最终会漂流至此。代码中的一部分调用发送者的方法以及希望发送的数据另一部分则检查接收端收到到达的消息。当发送者或接收者任一被丢弃时可以认为通道被 **关闭**_closed_)了
这里,我们将开发一个程序,它会在一个线程生成值向通道发送,而在另一个线程会接收值并打印出来。这里会通过通道在线程间发送简单值来演示这个功能。一旦你熟悉了这项技术,就能使用通道来实现聊天系统或利用很多线程进行分布式计算并将部分计算结果发送给一个线程进行聚合。
@ -27,9 +26,9 @@ fn main() {
<span class="caption">示例 16-6: 创建一个通道,并将其两端赋值给 `tx``rx`</span>
这里使用 `mpsc::channel` 函数创建一个新的通道;`mpsc` 是 **多个生产者,单个消费者***multiple producer, single consumer*的缩写。简而言之Rust 标准库实现通道的方式意味着一个通道可以有多个产生值的 **发送***sending*)端,但只能有一个消费这些值的 **接收***receiving*)端。想象一下多条小河小溪最终汇聚成大河:所有通过这些小河发出的东西最后都会来到大河的下游。目前我们以单个生产者开始,但是当示例可以工作后会增加多个生产者。
这里使用 `mpsc::channel` 函数创建一个新的通道;`mpsc` 是 **多个生产者,单个消费者**_multiple producer, single consumer_的缩写。简而言之Rust 标准库实现通道的方式意味着一个通道可以有多个产生值的 **发送**_sending_但只能有一个消费这些值的 **接收**_receiving_)端。想象一下多条小河小溪最终汇聚成大河:所有通过这些小河发出的东西最后都会来到大河的下游。目前我们以单个生产者开始,但是当示例可以工作后会增加多个生产者。
`mpsc::channel` 函数返回一个元组:第一个元素是发送端,而第二个元素是接收端。由于历史原因,`tx` 和 `rx` 通常作为 **发送者***transmitter*)和 **接收者***receiver*)的缩写,所以这就是我们将用来绑定这两端变量的名字。这里使用了一个 `let` 语句和模式来解构了此元组;第十八章会讨论 `let` 语句中的模式和解构。如此使用 `let` 语句是一个方便提取 `mpsc::channel` 返回的元组中一部分的手段。
`mpsc::channel` 函数返回一个元组:第一个元素是发送端,而第二个元素是接收端。由于历史原因,`tx` 和 `rx` 通常作为 **发送者**_transmitter_**接收者**_receiver_)的缩写,所以这就是我们将用来绑定这两端变量的名字。这里使用了一个 `let` 语句和模式来解构了此元组;第十八章会讨论 `let` 语句中的模式和解构。如此使用 `let` 语句是一个方便提取 `mpsc::channel` 返回的元组中一部分的手段。
让我们将发送端移动到一个新建线程中并发送一个字符串,这样新建线程就可以和主线程通讯了,如示例 16-7 所示。这类似与在河的上游扔下一只橡皮鸭或从一个线程向另一个线程发送聊天信息:
@ -53,7 +52,7 @@ fn main() {
这里再次使用 `thread::spawn` 来创建一个新线程并使用 `move``tx` 移动到闭包中这样新建线程就拥有 `tx` 了。新建线程需要拥有通道的发送端以便能向通道发送消息。
通道的发送端有一个 `send` 方法用来获取需要放入通道的值。`send` 方法返回一个 `Result<T, E>` 类型,所以如果接收端已经被丢弃了,将没有发送值的目标,所以发送操作会返回错误。在这个例子中,出错的时候调用 `unwrap` 产生 panic。过对于一个真实程序需要合理处理它:回到第九章复习正确处理错误的策略。
通道的发送端有一个 `send` 方法用来获取需要放入通道的值。`send` 方法返回一个 `Result<T, E>` 类型,所以如果接收端已经被丢弃了,将没有发送值的目标,所以发送操作会返回错误。在这个例子中,出错的时候调用 `unwrap` 产生 panic。过对于一个真实程序需要合理处理它:回到第九章复习正确处理错误的策略。
在示例 16-8 中,我们在主线程中从通道的接收端获取值。这类似于在河的下游捞起橡皮鸭或接收聊天信息:
@ -78,7 +77,7 @@ fn main() {
<span class="caption">示例 16-8: 在主线程中接收并打印内容 “hi”</span>
通道的接收端有两个有用的方法:`recv` 和 `try_recv`。这里,我们使用了 `recv`,它是 *receive* 的缩写。这个方法会阻塞主线程执行直到从通道中接收一个值。一旦发送了一个值,`recv` 会在一个 `Result<T, E>` 中返回它。当通道发送端关闭,`recv` 会返回一个错误表明不会再有新的值到来了。
通道的接收端有两个有用的方法:`recv` 和 `try_recv`。这里,我们使用了 `recv`,它是 _receive_ 的缩写。这个方法会阻塞主线程执行直到从通道中接收一个值。一旦发送了一个值,`recv` 会在一个 `Result<T, E>` 中返回它。当通道发送端关闭,`recv` 会返回一个错误表明不会再有新的值到来了。
`try_recv` 不会阻塞,相反它立刻返回一个 `Result<T, E>``Ok` 值包含可用的信息,而 `Err` 值代表此时没有任何消息。如果线程在等待消息过程中还有其他工作时使用 `try_recv` 很有用:可以编写一个循环来频繁调用 `try_recv`,再有可用消息时进行处理,其余时候则处理一会其他工作直到再次检查。
@ -188,7 +187,7 @@ Got: thread
### 通过克隆发送者来创建多个生产者
之前我们提到了`mpsc`是 *multiple producer, single consumer* 的缩写。可以运用 `mpsc` 来扩展示例 16-11 中的代码来以创建都向同一接收者发送值的多个线程。这可以通过克隆通道的发送端在来做到,如示例 16-11 所示:
之前我们提到了`mpsc`是 _multiple producer, single consumer_ 的缩写。可以运用 `mpsc` 来扩展示例 16-11 中的代码来以创建都向同一接收者发送值的多个线程。这可以通过克隆通道的发送端在来做到,如示例 16-11 所示:
<span class="filename">文件名: src/main.rs</span>

View File

@ -1,7 +1,6 @@
## 共享状态并发
> [ch16-03-shared-state.md](https://github.com/rust-lang/book/blob/master/src/ch16-03-shared-state.md)
> <br>
> [ch16-03-shared-state.md](https://github.com/rust-lang/book/blob/master/src/ch16-03-shared-state.md) > <br>
> commit ef072458f903775e91ea9e21356154bc57ee31da
虽然消息传递是一个很好的处理并发的方式,但并不是唯一一个。再一次思考一下 Go 编程语言文档中口号的这一部分“不要通过共享内存来通讯”“do not communicate by sharing memory.”):
@ -14,7 +13,7 @@
### 互斥器一次只允许一个线程访问数据
**互斥器***mutex*)是 *mutual exclusion* 的缩写,也就是说,任意时刻,其只允许一个线程访问某些数据。为了访问互斥器中的数据,线程首先需要通过获取互斥器的 **锁***lock*)来表明其希望访问数据。锁是一个作为互斥器一部分的数据结构,它记录谁有数据的排他访问权。因此,我们描述互斥器为通过锁系统 **保护***guarding*)其数据。
**互斥器**_mutex__mutual exclusion_ 的缩写,也就是说,任意时刻,其只允许一个线程访问某些数据。为了访问互斥器中的数据,线程首先需要通过获取互斥器的 **锁**_lock_)来表明其希望访问数据。锁是一个作为互斥器一部分的数据结构,它记录谁有数据的排他访问权。因此,我们描述互斥器为通过锁系统 **保护**_guarding_)其数据。
互斥器以难以使用著称,因为你不得不记住:
@ -172,7 +171,7 @@ is not implemented for `std::rc::Rc<std::sync::Mutex<i32>>`
#### 原子引用计数 `Arc<T>`
所幸 `Arc<T>` **正是** 这么一个类似 `Rc<T>` 并可以安全的用于并发环境的类型。字母 “a” 代表 **原子性***atomic*),所以这是一个**原子引用计数***atomically reference counted*)类型。原子性是另一类这里还未涉及到的并发原语:请查看标准库中 `std::sync::atomic` 的文档来获取更多细节。其中的要点就是:原子性类型工作起来类似原始类型,不过可以安全的在线程间共享。
所幸 `Arc<T>` **正是** 这么一个类似 `Rc<T>` 并可以安全的用于并发环境的类型。字母 “a” 代表 **原子性**_atomic_所以这是一个**原子引用计数**_atomically reference counted_)类型。原子性是另一类这里还未涉及到的并发原语:请查看标准库中 `std::sync::atomic` 的文档来获取更多细节。其中的要点就是:原子性类型工作起来类似原始类型,不过可以安全的在线程间共享。
你可能会好奇为什么不是所有的原始类型都是原子性的?为什么不是所有标准库中的类型都默认使用 `Arc<T>` 实现?原因在于线程安全带有性能惩罚,我们希望只在必要时才为此买单。如果只是在单线程中对值进行操作,原子性提供的保证并无必要,代码可以因此运行的更快。
@ -220,6 +219,6 @@ Result: 10
你可能注意到了,因为 `counter` 是不可变的,不过可以获取其内部值的可变引用;这意味着 `Mutex<T>` 提供了内部可变性,就像 `Cell` 系列类型那样。正如第十五章中使用 `RefCell<T>` 可以改变 `Rc<T>` 中的内容那样,同样的可以使用 `Mutex<T>` 来改变 `Arc<T>` 中的内容。
另一个值得注意的细节是 Rust 不能避免使用 `Mutex<T>` 的全部逻辑错误。回忆一下第十五章使用 `Rc<T>` 就有造成引用循环的风险,这时两个 `Rc<T>` 值相互引用,造成内存泄露。同理,`Mutex<T>` 也有造成 **死锁***deadlock* 的风险。这发生于当一个操作需要锁住两个资源而两个线程各持一个锁,这会造成它们永远相互等待。如果你对这个主题感兴趣,尝试编写一个带有死锁的 Rust 程序,接着研究任何其他语言中使用互斥器的死锁规避策略并尝试在 Rust 中实现他们。标准库中 `Mutex<T>``MutexGuard` 的 API 文档会提供有用的信息。
另一个值得注意的细节是 Rust 不能避免使用 `Mutex<T>` 的全部逻辑错误。回忆一下第十五章使用 `Rc<T>` 就有造成引用循环的风险,这时两个 `Rc<T>` 值相互引用,造成内存泄露。同理,`Mutex<T>` 也有造成 **死锁**_deadlock_ 的风险。这发生于当一个操作需要锁住两个资源而两个线程各持一个锁,这会造成它们永远相互等待。如果你对这个主题感兴趣,尝试编写一个带有死锁的 Rust 程序,接着研究任何其他语言中使用互斥器的死锁规避策略并尝试在 Rust 中实现他们。标准库中 `Mutex<T>``MutexGuard` 的 API 文档会提供有用的信息。
接下来,为了丰富本章的内容,让我们讨论一下 `Send``Sync` trait 以及如何对自定义类型使用他们。

View File

@ -1,7 +1,6 @@
## 使用 `Sync``Send` trait 的可扩展并发
> [ch16-04-extensible-concurrency-sync-and-send.md](https://github.com/rust-lang/book/blob/master/src/ch16-04-extensible-concurrency-sync-and-send.md)
> <br>
> [ch16-04-extensible-concurrency-sync-and-send.md](https://github.com/rust-lang/book/blob/master/src/ch16-04-extensible-concurrency-sync-and-send.md) > <br>
> commit 426f3e4ec17e539ae9905ba559411169d303a031
Rust 的并发模型中一个有趣的方面是:语言本身对并发知之 **甚少**。我们之前讨论的几乎所有内容,都属于标准库,而不是语言本身的内容。由于不需要语言提供并发相关的基础设施,并发方案不受标准库或语言所限:我们可以编写自己的或使用别人编写的并发功能。
@ -28,7 +27,7 @@ Rust 的并发模型中一个有趣的方面是:语言本身对并发知之 **
手动实现这些标记 trait 涉及到编写不安全的 Rust 代码,第十九章将会讲述具体的方法;当前重要的是,在创建新的由不是 `Send``Sync` 的部分构成的并发类型时需要多加小心,以确保维持其安全保证。[The Rustonomicon] 中有更多关于这些保证以及如何维持他们的信息。
[The Rustonomicon]: https://doc.rust-lang.org/stable/nomicon/
[the rustonomicon]: https://doc.rust-lang.org/stable/nomicon/
## 总结
@ -40,5 +39,4 @@ Rust 提供了用于消息传递的通道,和像 `Mutex<T>` 和 `Arc<T>` 这
接下来,让我们讨论一下当 Rust 程序变得更大时,有哪些符合语言习惯的问题建模方法和结构化解决方案,以及 Rust 的风格是如何与面向对象编程Object Oriented Programming中那些你所熟悉的概念相联系的。
[sharing-a-mutext-between-multiple-threads]:
ch16-03-shared-state.html#sharing-a-mutext-between-multiple-threads
[sharing-a-mutext-between-multiple-threads]: ch16-03-shared-state.html#sharing-a-mutext-between-multiple-threads