mirror of
https://github.com/KaiserY/trpl-zh-cn
synced 2024-11-09 08:51:18 +08:00
fix: typo
This commit is contained in:
parent
06a13017f1
commit
9a36a43d48
@ -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`,像这样:
|
||||
|
||||
|
@ -12,7 +12,7 @@
|
||||
|
||||
第三个问题是如果打开文件失败我们使用 `expect` 来打印出错误信息,不过这个错误信息只是说 `file not found`。除了缺少文件之外还有很多可以导致打开文件失败的方式:例如,文件可能存在,不过可能没有打开它的权限。如果我们现在就出于这种情况,打印出的 `file not found` 错误信息就给了用户错误的建议!
|
||||
|
||||
第四,我们不停的使用 `expect` 来处理不同的错误,如果用户没有指定足够的参数来运行程序,他们会从 Rust 得到 `index out of bounds` 错误,而这并不能明确的解释问题。如果所有的错误处理都位于一处这样将来的维护者在需要修改错误处理逻辑时就只需要考虑这一处代码。将所有的错误处理都放在一处也有助于确保我们打印的错误信息对终端用户来说是有意义的。
|
||||
第四,我们不停地使用 `expect` 来处理不同的错误,如果用户没有指定足够的参数来运行程序,他们会从 Rust 得到 `index out of bounds` 错误,而这并不能明确地解释问题。如果所有的错误处理都位于一处,这样将来的维护者在需要修改错误处理逻辑时就只需要考虑这一处代码。将所有的错误处理都放在一处也有助于确保我们打印的错误信息对终端用户来说是有意义的。
|
||||
|
||||
让我们通过重构项目来解决这些问题。
|
||||
|
||||
|
@ -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` 并指定希望撤回的版本:
|
||||
|
||||
|
@ -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” 的概念,他们代表无效或缺失的值。
|
||||
|
||||
|
@ -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`。第二种情况表明对于可变引用也有着相同的行为。
|
||||
|
||||
|
@ -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` 只会在值不再被使用时被调用一次。
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 所示:
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user