diff --git a/.travis.yml b/.travis.yml
index 1ea0074..9f7930e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,7 +3,7 @@ dist: trusty
language: rust
cache: cargo
rust:
- - nightly
+ - beta # Change this to stable when Rust 1.31.0 is out
branches:
only:
- master
diff --git a/README.md b/README.md
index b02a942..876eb58 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,15 @@
-# Rust 程序设计语言(第二版) 简体中文版
+# Rust 程序设计语言(第二版 & 2018 edition) 简体中文版
[](https://travis-ci.org/KaiserY/trpl-zh-cn)
## 状态
-还在施工中。大部分章节已经可以阅读。具体状态请参见官方 [projects](https://github.com/rust-lang/book/projects/1),`Frozen` 之后的内容应该较为稳定。
+2018 edition 的翻译迁移已基本完成,欢迎阅读学习!
-每章翻译开头都带有官方链接和 commit hash,若发现与官方不一致,欢迎 Issue 或 PR :)
+PS:
+
+* 对照源码位置:https://github.com/rust-lang/book/tree/master/src
+* 每章翻译开头都带有官方链接和 commit hash,若发现与官方不一致,欢迎 Issue 或 PR :)
## 静态页面构建与文档撰写
@@ -14,15 +17,15 @@
### 构建
-你可以将本mdbook构建成一系列静态html页面。这里我们采用[vuepress](https://vuepress.vuejs.org/zh/)打包出静态网页。在这之前,你需要安装[Nodejs](https://nodejs.org/zh-cn/)。
+你可以将本 mdbook 构建成一系列静态 html 页面。这里我们采用 [vuepress](https://vuepress.vuejs.org/zh/) 打包出静态网页。在这之前,你需要安装 [Nodejs](https://nodejs.org/zh-cn/)。
-全局安装vuepress
+全局安装 vuepress
``` bash
npm i -g vuepress
```
-cd到项目目录,然后开始构建。构建好的静态文档会出现在"./src/.vuepress/dist"中
+cd 到项目目录,然后开始构建。构建好的静态文档会出现在 "./src/.vuepress/dist" 中
```bash
vuepress build ./src
@@ -30,7 +33,7 @@ vuepress build ./src
### 文档撰写
-vuepress会启动一个本地服务器,并在浏览器对你保存的文档进行实时热更新。
+vuepress 会启动一个本地服务器,并在浏览器对你保存的文档进行实时热更新。
```bash
vuepress dev ./src
@@ -49,4 +52,4 @@ vuepress dev ./src
本翻译加速查看站点[上海站点http://rustdoc.saigao.fun](http://rustdoc.saigao.fun)
-[GitBook.com](https://www.gitbook.com/) 地址:[https://www.gitbook.com/book/kaisery/trpl-zh-cn/details](https://www.gitbook.com/book/kaisery/trpl-zh-cn/details)
+[GitBook.com](https://www.gitbook.com/) 地址:[https://legacy.gitbook.com/book/kaisery/trpl-zh-cn/details](https://legacy.gitbook.com/book/kaisery/trpl-zh-cn/details)
diff --git a/book.toml b/book.toml
index f34accd..f3bcb0e 100644
--- a/book.toml
+++ b/book.toml
@@ -5,3 +5,7 @@ description = "Rust 程序设计语言 简体中文版"
[build-dir]
destination = "mdbook"
+
+[output.html]
+additional-css = ["ferris.css"]
+additional-js = ["ferris.js"]
\ No newline at end of file
diff --git a/ferris.css b/ferris.css
new file mode 100644
index 0000000..b856d47
--- /dev/null
+++ b/ferris.css
@@ -0,0 +1,33 @@
+body.light .does_not_compile,
+body.light .panics,
+body.light .not_desired_behavior,
+body.rust .does_not_compile,
+body.rust .panics,
+body.rust .not_desired_behavior {
+ background: #fff1f1;
+}
+
+body.coal .does_not_compile,
+body.coal .panics,
+body.coal .not_desired_behavior,
+body.navy .does_not_compile,
+body.navy .panics,
+body.navy .not_desired_behavior,
+body.ayu .does_not_compile,
+body.ayu .panics,
+body.ayu .not_desired_behavior {
+ background: #501f21;
+}
+
+.ferris {
+ position: absolute;
+ z-index: 99;
+ right: 5px;
+ top: 30px;
+ width: 10%;
+ height: auto;
+}
+
+.ferris-explain {
+ width: 100px;
+}
\ No newline at end of file
diff --git a/ferris.js b/ferris.js
new file mode 100644
index 0000000..8917365
--- /dev/null
+++ b/ferris.js
@@ -0,0 +1,51 @@
+var ferrisTypes = [
+ {
+ attr: 'does_not_compile',
+ title: '这些代码不能编译!'
+ },
+ {
+ attr: 'panics',
+ title: '这些代码会 panic!'
+ },
+ {
+ attr: 'unsafe',
+ title: '这些代码块包含不安全(unsafe)代码。'
+ },
+ {
+ attr: 'not_desired_behavior',
+ title: '这些代码不会产生期望的行为。'
+ }
+ ]
+
+ document.addEventListener('DOMContentLoaded', () => {
+ for (var ferrisType of ferrisTypes) {
+ attachFerrises(ferrisType)
+ }
+ })
+
+ function attachFerrises (type) {
+ var elements = document.getElementsByClassName(type.attr)
+
+ for (var codeBlock of elements) {
+ var lines = codeBlock.textContent.split(/\r|\r\n|\n/).length - 1;
+
+ if (lines >= 4) {
+ attachFerris(codeBlock, type)
+ }
+ }
+ }
+
+ function attachFerris (element, type) {
+ var a = document.createElement('a')
+ a.setAttribute('href', 'ch00-00-introduction.html#ferris')
+ a.setAttribute('target', '_blank')
+
+ var img = document.createElement('img')
+ img.setAttribute('src', 'img/ferris/' + type.attr + '.svg')
+ img.setAttribute('title', type.title)
+ img.className = 'ferris'
+
+ a.appendChild(img)
+
+ element.parentElement.insertBefore(a, element)
+ }
\ No newline at end of file
diff --git a/src/PREFACE.md b/src/PREFACE.md
index 24a75b3..5328a4e 100644
--- a/src/PREFACE.md
+++ b/src/PREFACE.md
@@ -1 +1 @@
-# Rust 程序设计语言(第二版) 简体中文版
+# Rust 程序设计语言(第二版 & 2018 edition)简体中文版
diff --git a/src/SUMMARY.md b/src/SUMMARY.md
index b217cf3..0c0e228 100644
--- a/src/SUMMARY.md
+++ b/src/SUMMARY.md
@@ -36,10 +36,9 @@
## 基本 Rust 技能
-- [模块](ch07-00-modules.md)
- - [`mod` 与文件系统](ch07-01-mod-and-the-filesystem.md)
- - [使用 `pub` 控制可见性](ch07-02-controlling-visibility-with-pub.md)
- - [在不同的模块中引用命名](ch07-03-importing-names-with-use.md)
+- [包、crate 与 模块](ch07-00-packages-crates-and-modules.md)
+ - [包和 crate 用来创建库和二进制项目](ch07-01-packages-and-crates-for-making-libraries-and-executables.md)
+ - [模块系统用来控制作用域和私有性](ch07-02-modules-and-use-to-control-scope-and-privacy.md)
- [通用集合类型](ch08-00-common-collections.md)
- [vector](ch08-01-vectors.md)
@@ -116,6 +115,7 @@
- [高级 trait](ch19-03-advanced-traits.md)
- [高级类型](ch19-04-advanced-types.md)
- [高级函数与闭包](ch19-05-advanced-functions-and-closures.md)
+ - [宏](ch19-06-macros.md)
- [最后的项目: 构建多线程 web server](ch20-00-final-project-a-web-server.md)
- [单线程 web server](ch20-01-single-threaded.md)
@@ -126,7 +126,7 @@
- [A - 关键字](appendix-01-keywords.md)
- [B - 运算符与符号](appendix-02-operators.md)
- [C - 可派生的 trait](appendix-03-derivable-traits.md)
- - [D - 宏](appendix-04-macros.md)
- - [E - 本书翻译](appendix-05-translation.md)
- - [F - 最新功能](appendix-06-newest-features.md)
+ - [D - 实用开发工具](appendix-04-useful-development-tools.md)
+ - [E - 版本](appendix-05-editions.md)
+ - [F - 本书译本](appendix-06-translation.md)
- [G - Rust 是如何开发的与 “Nightly Rust”](appendix-07-nightly-rust.md)
diff --git a/src/appendix-00.md b/src/appendix-00.md
index 6c9254a..2e8f317 100644
--- a/src/appendix-00.md
+++ b/src/appendix-00.md
@@ -1,7 +1,7 @@
# 附录
-> [appendix-00.md](https://github.com/rust-lang/book/blob/master/second-edition/src/appendix-00.md)
+> [appendix-00.md](https://github.com/rust-lang/book/blob/master/src/appendix-00.md)
>
-> commit 4f2dc564851dc04b271a2260c834643dfd86c724
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
-附录部分包含一些在你的Rust之旅中可能用到的参考资料。
\ No newline at end of file
+附录部分包含一些在你的 Rust 之旅中可能用到的参考资料。
\ No newline at end of file
diff --git a/src/appendix-01-keywords.md b/src/appendix-01-keywords.md
index af4427b..e2c4972 100644
--- a/src/appendix-01-keywords.md
+++ b/src/appendix-01-keywords.md
@@ -1,81 +1,109 @@
-## 附录A - 关键字
+## 附录 A: 关键字
-> [appendix-01-keywords.md](https://github.com/rust-lang/book/blob/master/second-edition/src/appendix-01-keywords.md)
+> [appendix-01-keywords.md](https://raw.githubusercontent.com/rust-lang/book/master/src/appendix-01-keywords.md)
>
-> commit 32215c1d96c9046c0b553a05fa5ec3ede2e125c3
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
-下面的列表中是Rust正在使用或者以后会用关键字。因此,这些关键字不能被用作标识符,例如
-函数、变量、参数、结构体、模块、crate、常量、宏、静态值、属性、类型、trait 或生命周期
+下面的列表包含 Rust 中正在使用或者以后会用到的关键字。因此,这些关键字不能被用作标识符(除了 [原始标识符][raw-identifiers]),这包括函数、变量、参数、结构体字段、模块、crate、常量、宏、静态值、属性、类型、trait 或生命周期
的名字。
### 目前正在使用的关键字
-* `as` - 强制类型转换或者对使用`use`和`extern crate`声明引入的项目重命名
+如下关键字目前有对应其描述的功能。
+
+* `as` - 强制类型转换,消除特定包含项的 trait 的歧义,或者对 `use` 和 `extern crate` 语句中的项重命名
* `break` - 立刻退出循环
-* `const` - 定义常量或者 **不变原生指针** (*constant raw pointers*)
-* `continue` - 跳出本次循环,进入下一次循环
-* `crate` - 引入一个外部 **crate** 或一个代表 **crate** 的宏变量
-* `else` - 创建 `if` 和 `if let` 控制流的分支
+* `const` - 定义常量或不变裸指针(constant raw pointer)
+* `continue` - 继续进入下一次循环迭代
+* `crate` - 链接(link)一个外部 **crate** 或一个代表宏定义的 **crate** 的宏变量
+* `dyn` - 动态分发 trait 对象
+* `else` - 作为 `if` 和 `if let` 控制流结构的 fallback
* `enum` - 定义一个枚举
-* `extern` - 引入一个外部 **crate** 、函数或变量
-* `false` - 布尔值 `false`
+* `extern` - 链接一个外部 **crate** 、函数或变量
+* `false` - 布尔字面值 `false`
* `fn` - 定义一个函数或 **函数指针类型** (*function pointer type*)
-* `for` - 遍历一个迭代器或实现一个 **trait**或者指定一个具体的生命周期
+* `for` - 遍历一个迭代器或实现一个 trait 或者指定一个更高级的生命周期
* `if` - 基于条件表达式的结果分支
-* `impl` - 实现一个方法或 **trait** 功能
-* `in` - for循环语法的一部分
+* `impl` - 实现自有或 trait 功能
+* `in` - `for` 循环语法的一部分
* `let` - 绑定一个变量
* `loop` - 无条件循环
* `match` - 模式匹配
* `mod` - 定义一个模块
-* `move` - 使闭包获取所有权
-* `mut` - 表示一个可变绑定
-* `pub` - 在结构体、`impl`块或模块中表示可以被外部使用
-* `ref` - 绑定一个引用
+* `move` - 使闭包获取其所捕获项的所有权
+* `mut` - 表示引用、裸指针或模式绑定的可变性性
+* `pub` - 表示结构体字段、`impl` 块或模块的公有可见性
+* `ref` - 通过引用绑定
* `return` - 从函数中返回
-* `Self` - 实现一个 **trait** 类型的类型别名
+* `Self` - 实现 trait 的类型的类型别名
* `self` - 表示方法本身或当前模块
* `static` - 表示全局变量或在整个程序执行期间保持其生命周期
* `struct` - 定义一个结构体
* `super` - 表示当前模块的父模块
-* `trait` - 定义一个 **trait**
-* `true` - 布尔值 `true`
-* `type` - 定义一个类型别名或相关联的类型
-* `unsafe` - 表示不安全的代码、函数、**traits** 或者方法实现
+* `trait` - 定义一个 trait
+* `true` - 布尔字面值 `true`
+* `type` - 定义一个类型别名或关联类型
+* `unsafe` - 表示不安全的代码、函数、trait 或实现
* `use` - 引入外部空间的符号
-* `where` - 表示一个类型约束 [\[For example\]][ch13-01]
+* `where` - 表示一个约束类型的从句
* `while` - 基于一个表达式的结果判断是否进行循环
-[ch13-01]: ch13-01-closures.html#使用带有泛型和-fn-trait-的闭包
-
-
-
+### 保留做将来使用的关键字
-### 未使用的保留字
-
-这些关键字没有目前任何功能,但是它们是 Rust 未来会使用的保留字。
+如下关键字没有任何功能,不过由 Rust 保留以备将来的应用。
* `abstract`
-* `alignof`
+* `async`
* `become`
* `box`
* `do`
* `final`
* `macro`
-* `offsetof`
* `override`
* `priv`
-* `proc`
-* `pure`
-* `sizeof`
+* `try`
* `typeof`
* `unsized`
* `virtual`
* `yield`
+
+### 原始标识符
+[raw-identifiers]: #raw-identifiers
+
+原始标识符(Raw identifiers)允许你使用通常不能使用的关键字,其带有 `r#` 前缀。
+
+例如,`match` 是关键字。如果尝试编译这个函数:
+
+```rust,ignore
+fn match(needle: &str, haystack: &str) -> bool {
+ haystack.contains(needle)
+}
+```
+
+会得到这个错误:
+
+```text
+error: expected identifier, found keyword `match`
+ --> src/main.rs:4:4
+ |
+4 | fn match(needle: &str, haystack: &str) -> bool {
+ | ^^^^^ expected identifier, found keyword
+```
+
+可以通过原始标识符编写:
+
+```rust
+fn r#match(needle: &str, haystack: &str) -> bool {
+ haystack.contains(needle)
+}
+
+fn main() {
+ assert!(r#match("foo", "foobar"));
+}
+```
+
+注意 `r#` 前缀需同时用于函数名和调用。
+
+#### 动机
+
+出于一些原因这个功能是实用的,不过其主要动机是解决跨版本问题。比如,`try` 在 2015 edition 中不是关键字,而在 2018 edition 则是。所以如果如果用 2015 edition 编写的库中带有 `try` 函数,在 2018 edition 中调用时就需要使用原始标识符。
\ No newline at end of file
diff --git a/src/appendix-02-operators.md b/src/appendix-02-operators.md
index 0d5d901..9304b45 100644
--- a/src/appendix-02-operators.md
+++ b/src/appendix-02-operators.md
@@ -1,21 +1,20 @@
-## 附录B - 运算符与符号
-> [appendix-02-operators.md](https://github.com/rust-lang/book/blob/master/second-edition/src/appendix-02-operators.md)
->
-> commit d50521fc08e51892cdf1edf5e35f3847a42f9432
+## 附录 B:运算符与符号
-[commit]: https://github.com/rust-lang/book/commit/d50521fc08e51892cdf1edf5e35f3847a42f9432
+> [appendix-02-operators.md](https://github.com/rust-lang/book/blob/master/src/appendix-02-operators.md)
+>
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
-该附录包含了 Rust 语法的词汇表,包括运算符以及其他的符号,这些符号以其自身或者在路径、泛型、trait bounds、宏、属性、注释、元组以及大括号的上下文中出现。
+该附录包含了 Rust 语法的词汇表,包括运算符以及其他的符号,这些符号单独出现或出现在路径、泛型、trait bounds、宏、属性、注释、元组以及大括号上下文中。
### 运算符
-表B-1包含了 Rust 中的运算符、运算符如何出现在上下文中的示例、简短解释以及该运算符是否可重载。如果一个运算符是可重载的,则该运算符上用于重载的相关 trait 也会列出。
+表 B-1 包含了 Rust 中的运算符、运算符如何出现在上下文中的示例、简短解释以及该运算符是否可重载。如果一个运算符是可重载的,则该运算符上用于重载的相关 trait 也会列出。
-表 B-1: 运算符
+表 B-1: 运算符
| 运算符 | 示例 | 解释 | 是否可重载 |
|----------|---------|-------------|---------------|
-| `!` | `ident!(...)`, `ident!{...}`, `ident![...]` | 宏扩展 | |
+| `!` | `ident!(...)`, `ident!{...}`, `ident![...]` | 宏展开 | |
| `!` | `!expr` | 按位非或逻辑非 | `Not` |
| `!=` | `var != expr` | 不等比较 | `PartialEq` |
| `%` | `expr % expr` | 算术取模 | `Rem` |
@@ -28,7 +27,7 @@
| `*` | `expr * expr` | 算术乘法 | `Mul` |
| `*=` | `var *= expr` | 算术乘法与赋值 | `MulAssign` |
| `*` | `*expr` | 解引用 | |
-| `*` | `*const type`, `*mut type` | 原生指针 | |
+| `*` | `*const type`, `*mut type` | 裸指针 | |
| `+` | `trait + trait`, `'a + trait` | 复合类型限制 | |
| `+` | `expr + expr` | 算术加法 | `Add` |
| `+=` | `var += expr` | 算术加法与赋值 | `AddAssign` |
@@ -75,32 +74,34 @@
表 B-2 展示了以其自身出现以及出现在合法其他各个地方的符号。
-表 B-2:独立语法
+表 B-2:独立语法
| 符号 | 解释 |
|--------|-------------|
| `'ident` | 命名生命周期或循环标签 |
| `...u8`, `...i32`, `...f64`, `...usize`, 等 | 指定类型的数值常量 |
| `"..."` | 字符串常量 |
-| `r"..."`, `r#"..."#`, `r##"..."##`, etc. | 原生字符串常量, 未处理的遗漏字符 |
-| `b"..."` | 字节字符串; 构造一个 `[u8]` 类型而非字符串 |
-| `br"..."`, `br#"..."#`, `br##"..."##`, 等 | 原生字节字符串常量,原生字节和字节结合的字符串 |
-| `'...'` | 字符常量 |
-| `b'...'` | ASCII码字节常量 |
-| \|...\| expr
| 结束 |
-| `!` | 对一个离散函数来说最后总是空类型 |
-| `_` | “忽略”模式绑定, 也用于整数常量的可读性 |
+| `r"..."`, `r#"..."#`, `r##"..."##`, etc. | 原始字符串字面值, 未处理的转义字符 |
+| `b"..."` | 字节字符串字面值; 构造一个 `[u8]` 类型而非字符串 |
+| `br"..."`, `br#"..."#`, `br##"..."##`, 等 | 原始字节字符串字面值,原始和字节字符串字面值的结合 |
+| `'...'` | 字符字面值 |
+| `b'...'` | ASCII 码字节字面值 |
+| \|...\| expr
| 闭包 |
+| `!` | 离散函数的总是为空的类型 |
+| `_` | “忽略” 模式绑定;也用于增强整型字面值的可读性 |
+
+表 B-3 展示了出现在从模块结构到项的路径上下文中的符号
-表 B-3: 路径相关语法
+表 B-3:路径相关语法
| 符号 | 解释 |
|--------|-------------|
| `ident::ident` | 命名空间路径 |
-| `::path` | 与crate根相关的路径(如一个明确的绝对路径) |
-| `self::path` | 当前模块相关路径(如一个明确相关路径)|
-| `super::path` | 父模块相关路径 |
-| `type::ident`, `::ident` | 相关常量、函数以及类型 |
-| `::...` | 不可以被直接命名的相关项类型(如 `<&T>::...`,`<[T]>::...`, 等) |
+| `::path` | 与 crate 根相对的路径(如一个显式绝对路径) |
+| `self::path` | 与当前模块相对的路径(如一个显式相对路径)|
+| `super::path` | 与父模块相对的路径 |
+| `type::ident`, `::ident` | 关联常量、函数以及类型 |
+| `::...` | 不可以被直接命名的关联项类型(如 `<&T>::...`,`<[T]>::...`, 等) |
| `trait::method(...)` | 通过命名定义的 trait 来消除方法调用的二义性 |
| `type::method(...)` | 通过命名定义的类型来消除方法调用的二义性 |
| `::method(...)` | 通过命名 trait 和类型来消除方法调用的二义性 |
@@ -108,12 +109,12 @@
表 B-4 展示了出现在泛型类型参数上下文中的符号。
-表 B-4:泛型
+表 B-4:泛型
| 符号 | 解释 |
|--------|-------------|
| `path<...>` | 为一个类型中的泛型指定具体参数(如 `Vec`) |
-| `path::<...>`, `method::<...>` | 为一个泛型、函数或表达式中的方法指定具体参数,通常指 [turbofish][turbofish] (如 `"42".parse::()`)|
+| `path::<...>`, `method::<...>` | 为一个泛型、函数或表达式中的方法指定具体参数,通常指 turbofish(如 `"42".parse::()`)|
| `fn ident<...> ...` | 泛型函数定义 |
| `struct ident<...> ...` | 泛型结构体定义 |
| `enum ident<...> ...` | 泛型枚举定义 |
@@ -121,11 +122,9 @@
| `for<...> type` | 高级生命周期限制 |
| `type` | 泛型,其一个或多个相关类型必须被指定为特定类型(如 `Iterator- `)|
-[turbofish]: https://matematikaadit.github.io/posts/rust-turbofish.html
-
表 B-5 展示了出现在使用 trait bounds 约束泛型参数上下文中的符号。
-表 B-5: Trait Bound 约束
+表 B-5: Trait Bound 约束
| 符号 | 解释 |
|--------|-------------|
@@ -138,7 +137,7 @@
表 B-6 展示了在调用或定义宏以及在其上指定属性时的上下文中出现的符号。
-表 B-6: 宏与属性
+表 B-6: 宏与属性
| 符号 | 解释 |
|--------|-------------|
@@ -150,7 +149,7 @@
表 B-7 展示了写注释的符号。
-表 B-7: 注释
+表 B-7: 注释
| 符号 | 注释 |
|--------|-------------|
@@ -163,28 +162,32 @@
表 B-8 展示了出现在使用元组时上下文中的符号。
+表 B-8: 元组
+
| 符号 | 解释 |
|--------|-------------|
-| `()` | 空元祖(亦称单元), 用于常量或类型中 |
+| `()` | 空元组(亦称单元),即是字面值也是类型 |
| `(expr)` | 括号表达式 |
| `(expr,)` | 单一元素元组表达式 |
| `(type,)` | 单一元素元组类型 |
| `(expr, ...)` | 元组表达式 |
| `(type, ...)` | 元组类型 |
-| `expr(expr, ...)` | 函数调用表达式; 也用于初始化元组结构体 `struct` 以及元组枚举 `enum` 变体 |
+| `expr(expr, ...)` | 函数调用表达式;也用于初始化元组结构体 `struct` 以及元组枚举 `enum` 变体 |
| `ident!(...)`, `ident!{...}`, `ident![...]` | 宏调用 |
| `expr.0`, `expr.1`, etc. | 元组索引 |
-表 B-9 使用大括号的符号。
+表 B-9 展示了使用大括号的上下文。
+
+表 B-9: 大括号
| 符号 | 解释 |
|---------|-------------|
| `{...}` | 块表达式 |
-| `Type {...}` | `struct` |
+| `Type {...}` | `struct` 字面值 |
-表 B-10 展示了使用方括号的符号。
+表 B-10 展示了使用方括号的上下文。
-表 B-10: 方括号
+表 B-10: 方括号
| 符号 | 解释 |
|---------|-------------|
@@ -192,4 +195,4 @@
| `[expr; len]` | 复制了 `len`个 `expr`的数组 |
| `[type; len]` | 包含 `len`个 `type` 类型的数组|
| `expr[expr]` | 集合索引。 重载(`Index`, `IndexMut`) |
-| `expr[..]`, `expr[a..]`, `expr[..b]`, `expr[a..b]` | 集合索引,使用 `Range`,`RangeFrom`,`RangeTo` 或 `RangeFull` 作为索引来代替集合切片 |
+| `expr[..]`, `expr[a..]`, `expr[..b]`, `expr[a..b]` | 集合索引,使用 `Range`,`RangeFrom`,`RangeTo` 或 `RangeFull` 作为索引来代替集合 slice |
diff --git a/src/appendix-03-derivable-traits.md b/src/appendix-03-derivable-traits.md
index 2a86f8f..6411766 100644
--- a/src/appendix-03-derivable-traits.md
+++ b/src/appendix-03-derivable-traits.md
@@ -1,15 +1,12 @@
-## 附录C - 可派生的 trait
+## 附录 C:可派生的 trait
-> [appendix-03-derivable-traits.md][appendix-03]
+> [appendix-03-derivable-traits.md](https://github.com/rust-lang/book/blob/master/src/appendix-03-derivable-traits.md)
>
-> commit 32215c1d96c9046c0b553a05fa5ec3ede2e125c3
+> commit a86c1d315789b3ca13b20d50ad5005c62bdd9e37
-[appendix-03]: https://github.com/rust-lang/book/blob/master/second-edition/src/appendix-03-derivable-traits.md
-[commit]: https://github.com/rust-lang/book/commit/32215c1d96c9046c0b553a05fa5ec3ede2e125c3
+在本书的各个部分中,我们讨论了可应用于结构体和枚举定义的 `derive` 属性。`derive` 属性会在使用 `derive` 语法标记的类型上生成对应 trait 的默认实现的代码。
-在本书的各个部分中,我们讨论了可应用于结构体和枚举的 `derive` 属性。`derive` 属性生成的代码在使用 `derive` 语法注释的类型之上实现了带有默认实现的 trait 。
-
-在该附录中,我们提供标准库中所有可以使用 `derive` 的 trait 的参考。每部分都包含:
+在本附录中提供了标准库中所有所有可以使用 `derive` 的 trait 的参考。这些部分涉及到:
* 该 trait 将会派生什么样的操作符和方法
* 由 `derive` 提供什么样的 trait 实现
@@ -17,31 +14,31 @@
* 是否允许实现该 trait 的条件
* 需要 trait 操作的例子
-与 `derive` 属性提供的行为相比如果你需要与之不同的行为,请查阅标准库文档以获取每个 trait 的详情,来手动实现它们。
+如果你希望不同于 `derive` 属性所提供的行为,请查阅 [标准库文档](https://doc.rust-lang.org/std/index.html) 中每个 trait 的细节以了解如何手动实现它们。
-在类型上无法使用 `derive` 实现标准库的其余 trait。这些 trait 没有合理的默认行为, 因此,你可以以一种尝试完成的合理方式实现它们。
+标准库中定义的其它 trait 不能通过 `derive` 在类型上实现。这些 trait 不存在有意义的默认行为,所以由你负责以合理的方式实现它们。
-一个无法被派生的 trait 的例子是为最终用户处理格式化的 `Display` 。你应该时常考虑使用合适的方法来为最终用户显示一个类型。最终用户应该看到类型的什么部分?他们会找出相关部分吗?对他们来说,最相互关联的数据格式是什么样的?Rust 编译器没有这样的洞察力,因此,无法为你提供合适的默认行为。
+一个无法被派生的 trait 的例子是为终端用户处理格式化的 `Display` 。你应该时常考虑使用合适的方法来为终端用户显示一个类型。终端用户应该看到类型的什么部分?他们会找出相关部分吗?对他们来说最相关的数据格式是什么样的?Rust 编译器没有这样的洞察力,因此无法为你提供合适的默认行为。
-本附录所提供的可派生 trait 列表并不全面:库可以为它们自己的 trait 实现 `derive` , 让可以使用 `derive` 的 trait 列表真诚的开放。实现 `derive` 涉及使用程序化宏,这在附录D中有介绍。
+本附录所提供的可派生 trait 列表并不全面:库可以为其自己的 trait 实现 `derive`,可以使用 `derive` 的 trait 列表事实上是无限的。实现 `derive` 涉及到过程宏的应用,这在第十九章的 “宏” 有介绍。
-## 编程人员输出的 `Debug`
+### 用于程序员输出的 `Debug`
-`Debug` trait 在格式化字符串中使调试格式化,你可以在 `{}` 占位符里面加上 `:?` 显示它。
+`Debug` trait 用于开启格式化字符串中的调试格式,其通过在 `{}` 占位符中增加 `:?` 表明。
-`Debug` trait 允许你以调试目的来打印一个类型的实例,因此,使用类型的你以及其他的编程人员可以让程序在执行时在指定点上显示一个实例。
+`Debug` trait 允许以调试目的来打印一个类型的实例,所以使用该类型的程序员可以在程序执行的特定时间点观察其实例。
-例如,在使用 `assert_eq!` 宏时,Debug` trait 是必须的。如果等式断言失败,这个宏就把给定实例的值作为参数打印出来,因此,编程人员可以看到两个实例为什么不相等。
+例如,在使用 `assert_eq!` 宏时,`Debug` trait 是必须的。如果等式断言失败,这个宏就把给定实例的值作为参数打印出来,如此程序员可以看到两个实例为什么不相等。
## 等值比较的 `PartialEq` 和 `Eq`
-`PartialEq` trait 可以比较一个类型的实例以检查是否相等,并且可以使用 `==` 和 `!=` 操作符。
+`PartialEq` trait 可以比较一个类型的实例以检查是否相等,并开启了 `==` 和 `!=` 运算符的功能。
-派生的 `PartialEq` 实现了 `eq` 方法。当 `PartialEq` 在结构体上派生时,只有*所有*的字段都相等时两个实例才相等,同时只要有字段不相等则两个实例就不相等。当在枚举上派生时,每一个变体(variant)都和它自身相等,且和其他变体都不相等。
+派生的 `PartialEq` 实现了 `eq` 方法。当 `PartialEq` 在结构体上派生时,只有*所有* 的字段都相等时两个实例才相等,同时只要有任何字段不相等则两个实例就不相等。当在枚举上派生时,每一个成员都和其自身相等,且和其他成员都不相等。
例如,当使用 `assert_eq!` 宏时,需要比较比较一个类型的两个实例是否相等,则 `PartialEq` trait 是必须的。
-`Eq` trait 没有方法。其目的是为每一个注解类型的值作标志,其值等于其自身。 `Eq` trait 只能应用于那些实现了 `PartialEq` 的类型,但并非所有实现了 `PartialEq` 的类型可以实现 `Eq`。浮点类型就是一个例子:浮点数状态的实现,两个非数字(`NaN`,not-a-number)值是互不相等的。
+`Eq` trait 没有方法。其作用是表明每一个被标记类型的值等于其自身。`Eq` trait 只能应用于那些实现了 `PartialEq` 的类型,但并非所有实现了 `PartialEq` 的类型都可以实现 `Eq`。浮点类型就是一个例子:浮点数的实现表明两个非数字(`NaN`,not-a-number)值是互不相等的。
例如,对于一个 `HashMap` 中的 key 来说, `Eq` 是必须的,这样 `HashMap` 就可以知道两个 key 是否一样了。
@@ -49,19 +46,19 @@
`PartialOrd` trait 可以基于排序的目的而比较一个类型的实例。实现了 `PartialOrd` 的类型可以使用 `<`、 `>`、`<=` 和 `>=` 操作符。但只能在同时实现了 `PartialEq` 的类型上使用 `PartialOrd`。
-派生 `PartialOrd` 实现了 `partial_cmp` 方法,其返回一个 `Option` ,但当给定值无法产生顺序时将返回 `None`。尽管大多数类型的值都可以比较,但一个无法产生顺序的例子是:浮点类型的非数字值(`NaN`,not-a-number)。当在浮点数上调用 `partial_cmp` 时,`NaN` 的浮点数将返回 `None`。
+派生 `PartialOrd` 实现了 `partial_cmp` 方法,其返回一个 `Option` ,但当给定值无法产生顺序时将返回 `None`。尽管大多数类型的值都可以比较,但一个无法产生顺序的例子是:浮点类型的非数字值。当在浮点数上调用 `partial_cmp` 时,`NaN` 的浮点数将返回 `None`。
当在结构体上派生时,`PartialOrd` 以在结构体定义中字段出现的顺序比较每个字段的值来比较两个实例。当在枚举上派生时,认为在枚举定义中声明较早的枚举变体小于其后的变体。
例如,对于来自于 `rand` carte 中的 `gen_range` 方法来说,当在一个大值和小值指定的范围内生成一个随机值时,`PartialOrd` trait 是必须的。
-`Ord` trait 也让你明白在一个带注解类型上的任意两个值存在有效顺序。`Ord` trait 实现了 `cmp` 方法,它返回一个 `Ordering` 而不是 `Option`,因为总存在一个合法的顺序。只可以在实现了 `PartialOrd` 和 `Eq`( `Eq` 依赖 `PartialEq` )的类型上使用 `Ord` trait 。当在结构体或枚举上派生时, `cmp` 和以 `PartialOrd` 派生实现的 `partial_cmp` 表现一致。
+`Ord` trait 也让你明白在一个带注解类型上的任意两个值存在有效顺序。`Ord` trait 实现了 `cmp` 方法,它返回一个 `Ordering` 而不是 `Option`,因为总存在一个合法的顺序。只可以在实现了 `PartialOrd` 和 `Eq`(`Eq` 依赖 `PartialEq`)的类型上使用 `Ord` trait 。当在结构体或枚举上派生时, `cmp` 和以 `PartialOrd` 派生实现的 `partial_cmp` 表现一致。
-例如,当在 `BTreeSet` (一种基于有序值存储数据的数据结构)上存值时,`Ord` 是必须的。
+例如,当在 `BTreeSet`(一种基于有序值存储数据的数据结构)上存值时,`Ord` 是必须的。
## 复制值的 `Clone` 和 `Copy`
-`Clone` trait 可以明确地创建一个值的深拷贝( deep copy ),复制过程可能包含任意代码的执行以及堆上数据的复制。查阅第四章“变量和数据的交互方式:移动”以获取有关 `Clone` 的更多信息。
+`Clone` trait 可以明确地创建一个值的深拷贝(deep copy),复制过程可能包含任意代码的执行以及堆上数据的复制。查阅第四章 “变量和数据的交互方式:移动” 以获取有关 `Clone` 的更多信息。
派生 `Clone` 实现了 `clone` 方法,其为整个的类型实现时,在类型的每一部分上调用了 `clone` 方法。这意味着类型中所有字段或值也必须实现 `Clone` 来派生 `Clone` 。
diff --git a/src/appendix-04-useful-development-tools.md b/src/appendix-04-useful-development-tools.md
new file mode 100644
index 0000000..1d579df
--- /dev/null
+++ b/src/appendix-04-useful-development-tools.md
@@ -0,0 +1,168 @@
+## 附录 D:实用开发工具
+
+> [appendix-04-useful-development-tools.md](https://github.com/rust-lang/book/blob/master/src/appendix-04-useful-development-tools.md)
+>
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
+
+本附录,我们将讨论 Rust 项目提供的用于开发 Rust 代码的工具。
+
+## 通过 `rustfmt` 自动格式化
+
+`rustfmt` 工具根据社区代码风格格式化代码。很多项目使用 `rustfmt` 来避免编写 Rust 风格的争论:所有人都用这个工具格式化代码!
+
+`rustfmt` 工具的质量还未达到发布 1.0 版本的水平,不过目前有一个可用的预览版。请尝试使用并告诉我们它如何!
+
+安装 `rustfmt`:
+
+```text
+$ rustup component add rustfmt-preview
+```
+
+这会提供 `rustfmt` 和 `cargo-fmt`,类似于 Rust 同时安装 `rustc` 和 `cargo`。为了格式化整个 Cargo 项目:
+
+```text
+$ cargo fmt
+```
+
+运行此命令会格式化当前 crate 中所有的 Rust 代码。这应该只会改变代码风格,而不是代码语义。请查看 [该文档][rustfmt] 了解 `rustfmt` 的更多信息。
+
+[rustfmt]: https://github.com/rust-lang-nursery/rustfmt
+
+## 通过 `rustfix` 修复代码
+
+如果你编写过 Rust 代码,那么你可能见过编译器警告。例如,考虑如下代码:
+
+文件名: src/main.rs
+
+```rust
+fn do_something() {}
+
+fn main() {
+ for i in 0..100 {
+ do_something();
+ }
+}
+```
+
+这里调用了 `do_something` 函数 100 次,不过从未在 `for` 循环体中使用变量 `i`。Rust 会警告说:
+
+```text
+$ cargo build
+ Compiling myprogram v0.1.0 (file:///projects/myprogram)
+warning: unused variable: `i`
+ --> src/main.rs:4:9
+ |
+4 | for i in 1..100 {
+ | ^ help: consider using `_i` instead
+ |
+ = note: #[warn(unused_variables)] on by default
+
+ Finished dev [unoptimized + debuginfo] target(s) in 0.50s
+```
+
+警告中建议使用 `_i` 名称:下划线表明该变量有意不使用。我们可以通过 `cargo fix` 命令使用 `rustfix` 工具来自动采用该建议:
+
+```text
+$ cargo fix
+ Checking myprogram v0.1.0 (file:///projects/myprogram)
+ Fixing src/main.rs (1 fix)
+ Finished dev [unoptimized + debuginfo] target(s) in 0.59s
+```
+
+如果再次查看 *src/main.rs*,会发现 `cargo fix` 修改了代码:
+
+文件名: src/main.rs
+
+```rust
+fn do_something() {}
+
+fn main() {
+ for _i in 0..100 {
+ do_something();
+ }
+}
+```
+
+现在 `for` 循环变量变为 `_i`,警告也不再出现。
+
+`cargo fix` 命令可以用于在不同 Rust 版本间迁移代码。版本在附录 E 中介绍。
+
+## 通过 `clippy` 提供更多 lint 功能
+
+`clippy` 工具是一系列 lint 的集合,用于捕捉常见错误和改进 Rust 代码。
+
+`clippy` 工具的质量还未达到发布 1.0 版本的水平,不过目前有一个可用的预览版。请尝试使用并告诉我们它如何!
+
+安装 `clippy`:
+
+```text
+$ rustup component add clippy-preview
+```
+
+对任何 Cargo 项目运行 clippy 的 lint:
+
+```text
+$ cargo clippy
+```
+
+例如,如果程序使用了如 pi 这样数学常数的近似值,如下:
+
+文件名: src/main.rs
+
+```rust
+fn main() {
+ let x = 3.1415;
+ let r = 8.0;
+ println!("the area of the circle is {}", x * r * r);
+}
+```
+
+在此项目上运行 `cargo clippy` 会导致这个错误:
+
+```text
+error: approximate value of `f{32, 64}::consts::PI` found. Consider using it directly
+ --> src/main.rs:2:13
+ |
+2 | let x = 3.1415;
+ | ^^^^^^
+ |
+ = note: #[deny(clippy::approx_constant)] on by default
+ = help: for further information visit https://rust-lang-nursery.github.io/rust-clippy/v0.0.212/index.html#approx_constant
+```
+
+这告诉我们 Rust 定义了更为精确的常量,而如果使用了这些常量程序将更加准确。如下代码就不会导致 `clippy` 产生任何错误或警告:
+
+文件名: src/main.rs
+
+```rust
+fn main() {
+ let x = std::f64::consts::PI;
+ let r = 8.0;
+ println!("the area of the circle is {}", x * r * r);
+}
+```
+
+请查看 [其文档][clippy] 来了解 `clippy` 的更多信息。
+
+[clippy]: https://github.com/rust-lang-nursery/rust-clippy
+
+## 使用 Rust Language Server 的 IDE 集成
+
+为了帮助 IDE 集成,Rust 项目分发了 `rls`,其为 Rust Language Server 的缩写。这个工具采用 [Language Server Protocol][lsp],这是一个 IDE 与编程语言沟通的规格说明。`rls` 可以用于不同的客户端,比如 [Visual Studio: Code 的 Rust 插件][vscode]。
+
+[lsp]: http://langserver.org/
+[vscode]: https://marketplace.visualstudio.com/items?itemName=rust-lang.rust
+
+`rls` 工具的质量还未达到发布 1.0 版本的水平,不过目前有一个可用的预览版。请尝试使用并告诉我们它如何!
+
+安装 `rls`:
+
+```text
+$ rustup component add rls-preview
+```
+
+接着为特定的 IDE 安装 language server 支持,如比便会获得如自动补全、跳转到定义和 inline error 之类的功能。
+
+请查看 [其文档][rls] 来了解 `rls` 的更多信息。
+
+[rls]: https://github.com/rust-lang-nursery/rls
\ No newline at end of file
diff --git a/src/appendix-05-editions.md b/src/appendix-05-editions.md
new file mode 100644
index 0000000..3af7c35
--- /dev/null
+++ b/src/appendix-05-editions.md
@@ -0,0 +1,27 @@
+## 附录 E:版本
+
+> [appendix-05-editions.md](https://github.com/rust-lang/book/blob/master/src/appendix-05-editions.md)
+>
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
+
+早在第一章,我们见过 `cargo new` 在 *Cargo.toml* 中增加了一些有关 `edition` 的元数据。本附录将解释其意义!
+
+Rust 语言和编译器有一个为期 6 周的发布循环。这意味着用户会稳定得到新功能的更新。其他编程语言发布大更新但不甚频繁;Rust 选择更为频繁的发布小更新。一段时间之后,所有这些小更新会日积月累。不过随着版本的发布,很难回顾说 “哇,从 Rust 1.10 到 Rust 1.31,Rust 的变化真大!”
+
+每两到三年,Rust 团队会生成一个新的 Rust **版本**(*edition*)。每一个版本会结合已经落地的功能,并提供一个清晰的带有完整更新文档和工具的功能包。新版本会作为常规的 6 周发布过程的一部分发布。
+
+这为不同的人群提供了不同的功能:
+
+* 对于活跃的 Rust 用户,其将增量的修改与易于理解的功能包相结合。
+* 对于非用户,它表明发布了一些重大进展,这意味着 Rust 可能变得值得一试。
+* 对于 Rust 自身开发者,其提供了项目整体的集合点。
+
+在本文档编写时,Rust 有两个版本:Rust 2015 和 Rust 2018。本书基于 Rust 2018 edition 编写。
+
+*Cargo.toml* 中的 `edition` 字段表明代码应该使用哪个版本编译。如果该字段不存在,其默认为 `2015` 以提供后向兼容性。
+
+每个项目都可以选择不同于默认的 2015 edition 的版本。这样,版本可能会包含不兼容的修改,比如新增关键字可能会与代码中的标识符冲突并导致错误。不过除非选择兼容这些修改,(旧)代码仍将能够编译,即便升级了 Rust 编译器的版本。所有 Rust 编译器都支持任何之前存在的编译器版本,并可以链接人恶化支持版本的 crate。编译器修改只影响最初的解析代码的过程。因此,如果你使用 Rust 2015 而某个依赖使用 Rust 2018,你的项目仍旧能够编译并使用该依赖。反之,若项目使用 Rust 2018 而依赖使用 Rust 2015 亦可工作。
+
+有一点需要明确:大部分功能在所有版本中都能使用。开发者使用任何 Rust 版本将能继续接收最新稳定版的改进。然而在一些情况,主要是增加了新关键字的时候,则可能出现了只能用于新版本的功能。只需切换版本即可利用新版本的功能。
+
+请查看 [Edition Guide](https://rust-lang-nursery.github.io/edition-guide/) 了解更多细节,这是一个完全介绍版本的书籍,包括如何通过 `cargo fix` 自动将代码迁移到新版本。
\ No newline at end of file
diff --git a/src/appendix-06-translation.md b/src/appendix-06-translation.md
new file mode 100644
index 0000000..7e00418
--- /dev/null
+++ b/src/appendix-06-translation.md
@@ -0,0 +1,25 @@
+## 附录 F:本书译本
+
+> [appendix-06-translation.md](https://github.com/rust-lang/book/blob/master/src/appendix-06-translation.md)
+>
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
+
+一些非英语语言的资源。多数仍在翻译中;查阅 [翻译标签][label] 来帮助我们或使我们知道新的翻译!
+
+[label]: https://github.com/rust-lang/book/issues?q=is%3Aopen+is%3Aissue+label%3ATranslations
+
+- [Português](https://github.com/rust-br/rust-book-pt-br) (BR)
+- [Português](https://github.com/nunojesus/rust-book-pt-pt) (PT)
+- [Tiếng việt](https://github.com/hngnaig/rust-lang-book/tree/vi-VN)
+- [简体中文](http://www.broadview.com.cn/article/144), [另一个版本(译者注:已基本翻译完毕)](https://github.com/KaiserY/trpl-zh-cn)
+- [Українська](https://github.com/pavloslav/rust-book-uk-ua)
+- [Español](https://github.com/thecodix/book)
+- [Italiano](https://github.com/AgeOfWar/rust-book-it)
+- [Русский](https://github.com/iDeBugger/rust-book-ru)
+- [한국어](https://github.com/rinthel/rust-lang-book-ko)
+- [日本語](https://github.com/hazama-yuinyan/book)
+- [Français](https://github.com/quadrifoglio/rust-book-fr)
+- [Polski](https://github.com/paytchoo/book-pl)
+- [עברית](https://github.com/idanmel/rust-book-heb)
+- [Cebuano](https://github.com/agentzero1/book)
+- [Tagalog](https://github.com/josephace135/book)
\ No newline at end of file
diff --git a/src/appendix-07-nightly-rust.md b/src/appendix-07-nightly-rust.md
index 39b5963..23acb6c 100644
--- a/src/appendix-07-nightly-rust.md
+++ b/src/appendix-07-nightly-rust.md
@@ -1 +1,115 @@
-## G - Rust 是如何开发的与 “Nightly Rust”
+## 附录 G:Rust 是如何开发的与 “Nightly Rust”
+
+> [appendix-07-nightly-rust.md](https://github.com/rust-lang/book/blob/master/src/appendix-07-nightly-rust.md)
+>
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
+
+本附录介绍 Rust 是如何开发的以及这如何影响作为 Rust 开发者的你。
+
+### 无停滞稳定
+
+作为一个语言,Rust **十分** 注重代码的稳定性。我们希望 Rust 成为你代码坚实的基础,不过如果事物不断改变的话,这也将是不可能的。同时,当事物不再改变时,如果不能实验新功能的话,在发布之前我们也无法发现其中重大的缺陷。
+
+对于这个问题我们的解决方案被称为 “无停滞稳定”(“stability without stagnation”),其指导性原则是:无需担心升级到最新的稳定版 Rust。每次升级应该是无痛的,并应带来新功能,更少的 bug 和更快的编译速度。
+
+### Choo, Choo!(开车啦,逃)发布通道和发布时刻表(Riding the Trains)
+
+Rust 开发运行于一个 **发布时刻表**(*train schedule*)之上。也就是说,所有的开发工作都位于 Rust 仓库的 `master` 分支。发布采用 software release train 模型,其被用于 思科 IOS 等其它软件项目。Rust 有三个 **发布通道**(*release channel*):
+
+* Nightly
+* Beta
+* Stable (稳定版)
+
+大部分 Rust 开发者主要采用稳定版通道,不过希望实验新功能的开发者可能会使用 nightly 或 beta 版。
+
+如下是一个开发和发布过程如何运转的例子:假设 Rust 团队正在进行 Rust 1.5 的发布工作。该版本发布于 2015 年 12 月,不过这里只是为了提供一个真实的版本。Rust 新增了一项功能:一个 `master` 分支的新提交。每天晚上,会产生一个新的 nightly 版本。每天都是发布版本的日子,而这些发布由发布基础设施自动完成。所以随着时间推移,发布轨迹看起来像这样,版本一天一发:
+
+```text
+nightly: * - - * - - *
+```
+
+每 6 周时间,是准备发布新版本的时候了!Rust 仓库的 `beta` 分支会从用于 nightly 的 `master` 分支产生。现在,有了两个发布版本:
+
+```text
+nightly: * - - * - - *
+ |
+beta: *
+```
+
+大部分 Rust 用户不会主要使用 beta 版本,不过在 CI 系统中对 beta 版本进行测试能够帮助 Rust 发现可能的回归缺陷(regression)。同时,每天仍产生 nightly 发布:
+
+```text
+nightly: * - - * - - * - - * - - *
+ |
+beta: *
+```
+
+第一个 beta 版的 6 周后,是发布稳定版的时候了!`stable` 分支从 `beta` 分支生成:
+
+```text
+nightly: * - - * - - * - - * - - * - - * - * - *
+ |
+beta: * - - - - - - - - *
+ |
+stable: *
+```
+
+好的!Rust 1.5 发布了!然而,我们忘了些东西:因为又过了 6 周,我们还需发布 **新版** Rust 的 beta 版,Rust 1.6。所以从 `stable` 生成 `beta` 分支后,新版的 `beta` 分之也再次从 `nightly` 生成:
+
+```text
+nightly: * - - * - - * - - * - - * - - * - * - *
+ | |
+beta: * - - - - - - - - * *
+ |
+stable: *
+```
+
+这被称为 “train model”,因为每 6 周,一个版本 “离开车站”(“leaves the station”),不过从 beta 通道到达稳定通道还有一段旅程。
+
+Rust 每 6周发布一个版本,如时钟版准确。如果你知道了某个 Rust 版本的发布时间,就可以知道下个版本的时间:6 周后。每 6 周发布版本的一个好的方面是下一班车会来得更快。如果特定版本碰巧缺失某个功能也无需担心:另一个版本很快就会到来!这有助于减少因临近发版时间而偷偷释出未经完善的功能的压力。
+
+多亏了这个过程,你总是可以切换到下一版本的 Rut 并验证是否可以轻易的升级:如果 beta 版不能如期工作,你可以向 Rust 团队报告并在发布稳定版之前得到修复!beta 版造成的破坏是非常少见的,不过 `rustc` 也不过是一个软件,可能会存在 bug。
+
+### 不稳定功能
+
+这个发布模型中另一个值得注意的地方:不稳定功能(unstable features)。Rust 使用一个被称为 “功能标记”(“feature flags”)的技术来确定给定版本的某个功能是否启用。如果新功能正在积极底开发中,其提交到了 `master`,因此会出现在 nightly 版中,不过会位于一个 **功能标记** 之后。作为用户,如果你希望尝试这个正在开发的功能,则可以在源码中使用合适的标记来开启,不过必须使用 nightly 版。
+
+如果使用的是 beta 或稳定版 Rust,则不能使用任何功能标记。这是在新功能被标记未永久稳定之前获得实用价值的关键。这既满足了希望实用最尖端技术的同学,那些坚持稳定版的同学也知道其代码不会被破坏。这就是无停滞稳定。
+
+本书只包含稳定的功能,因为还在开发中的功能仍可能改变,当其进入稳定版时肯定会与编写本书的时候有所不同。你可以在网上获取 nightly 版的文档。
+
+### Rustup 和 Rust Nightly 的职责
+
+Rustup 使得改变不同发布通道的 Rust 更为简单,其在全局或分项目的层次工作。其默认会安装稳定版 Rust。例如为了安装 nightly:
+
+```text
+$ rustup install nightly
+```
+
+你会发现 `rustup` 也安装了所有的 **工具链**(*toolchains*, Rust 和其相关组件)。如下是一位作者的 Windows 计算机上的例子:
+
+```powershell
+> rustup toolchain list
+stable-x86_64-pc-windows-msvc (default)
+beta-x86_64-pc-windows-msvc
+nightly-x86_64-pc-windows-msvc
+```
+
+如你所见,默认是稳定版。大部分 Rust 用户在大部分时间使用稳定版。你可能也会这么做,不过如果你关心最新的功能,可以为特定项目使用 nightly 版。为此,可以在项目目录使用 `rustup override` 来设置当前目录 `rustup`使用 nightly 工具链作:
+
+```text
+$ cd ~/projects/needs-nightly
+$ rustup override set nightly
+```
+
+现在,每次在 *~/projects/needs-nightly* 调用 `rustc` 或 `cargo`,`rustup` 会确保使用 nightly 版 Rust。在这有很多 Rust 项目时大有裨益!
+
+### RFC 过程和团队
+
+那么你如何了解这些新功能呢?Rust 开发模式遵循一个 **Request For Comments (RFC) 过程**。如果你希望改进 Rust,可以编写一个提议,也就是 RFC。
+
+任何人都可以编写 RFC 来改进 Rust,同时这些 RFC 会被 Rust 团队评审和讨论,他们由很多不同分工的子团队组成。这里是 [Rust 官网上](https://www.rust-lang.org/en-US/team.html) 所有团队的总列表,其包含了项目中每个领域的团队:语言设计、编译器实现、基础设施、文档等。个个团队会阅读相应的提议和评论,编写回复,并最终达成接受或回绝功能的一致。
+
+如果功能被接受了,在 Rust 仓库会打开一个 issue,人们就可以实现它。实现功能的人当人可能不是最初提议功能的人!当实现完成后,其会合并到 `master` 分支并位于一个功能开关(feature gate)之后,正如 [“不稳定功能”(#unstable-features) 部分所讨论的。
+
+在稍后的某个时间,一旦使用 nightly 版的 Rust 团队能够尝试这个功能了,团队成员会讨论这个功能,它如何在 nightly 中工作,并决定是否应该进入稳定版。如果决定继续推进,功能开关会移除,然后这个功能就被认为是稳定的了!并会在新的稳定版 Rust 中出现。
\ No newline at end of file
diff --git a/src/ch00-00-introduction.md b/src/ch00-00-introduction.md
index 8fba530..974f626 100644
--- a/src/ch00-00-introduction.md
+++ b/src/ch00-00-introduction.md
@@ -1,8 +1,8 @@
# 介绍
-> [ch00-00-introduction.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch00-00-introduction.md)
+> [ch00-00-introduction.md](https://github.com/rust-lang/book/blob/master/src/ch00-00-introduction.md)
>
-> commit 7480e811ab5ad8d53a5b854d9b0c7a5a4f58499f
+> commit 0aa307c7d79d2cbf83cdf5d47780b2904e9cb03f
> 注意:本书的版本与出版的 [The Rust Programming Language][nsprust]
> 和电子版的 [No Starch Press][nsp] 一致
@@ -10,9 +10,7 @@
[nsprust]: https://nostarch.com/rust
[nsp]: https://nostarch.com/
-欢迎阅读 “Rust 程序设计语言”,一本介绍 Rust 的书。
-
-Rust 程序设计语言能帮你编写出更快、更可靠的软件。在其他编程语言设计中,高层工程学和底层控制往往不能兼得;Rust 则试图挑战该现象。通过权衡强大的技术能力与优秀的开发体验,Rust 允许你控制底层细节(比如内存使用),并免受过去做此类控制所经历的烦恼。
+欢迎阅读 “Rust 程序设计语言”,一本介绍 Rust 的书。Rust 程序设计语言能帮助你编写更快、更可靠的软件。在编程语言设计中,高层工程学和底层控制往往不能兼得;Rust 则试图挑战这一矛盾。通过权衡强大的技术能力与优秀的开发体验,Rust 允许你控制底层细节(比如内存使用),并免受以往进行此类控制所经受的所有烦恼。
## 谁会使用 Rust
@@ -20,11 +18,11 @@ Rust 因多种原因适用于很多开发者。让我们讨论几个最重要的
### 开发者团队
-Rust 被证明是可用于大型的、拥有不同层次系统编程知识的开发者团队间协作的高效工具。底层代码中容易出现大量隐晦的 bug,在其他编程语言中,只能通过大量的测试和经验丰富的开发者细心的代码评审来捕获。在 Rust 中,编译器充当了守门员的角色,它拒绝编译存在隐晦 bug 的代码,包括并发 bug。通过与编译器合作,团队将更多的时间聚焦在程序逻辑上,而不是追踪 bug。
+Rust 被证明是可用于大型的、拥有不同层次系统编程知识的开发者团队间协作的高效工具。底层代码中容易出现种种隐晦的 bug,在其他编程语言中,只能通过大量的测试和经验丰富的开发者细心的代码评审来捕获它们。在 Rust 中,编译器充当了守门员的角色,它拒绝编译存在这些难以捕获的 bug 的代码,这其中包括并发 bug。通过与编译器合作,团队将更多的时间聚焦在程序逻辑上,而不是追踪 bug。
-Rust 也将当前的开发工具带到了系统编程世界:
+Rust 也为系统编程世界带来了现代化的开发工具:
-* Cargo,内置的依赖管理器和构建工具,它能轻松增加、编译和管理依赖,并在 Rust 生态系统中保持一致。
+* Cargo,内置的依赖管理器和构建工具,它能轻松增加、编译和管理依赖,并使其在 Rust 生态系统中保持一致。
* Rustfmt 确保开发者遵循一致的代码风格。
* Rust Language Server 为集成开发环境(IDE)提供了强大的代码补全和内联错误信息功能。
@@ -32,7 +30,7 @@ Rust 也将当前的开发工具带到了系统编程世界:
### 学生
-Rust 适用于学生或任何对操作系统概念感兴趣的人。通过 Rust,很多人已经了解操作系统开发这样的主题。社区非常欢迎并乐于解答学生们的问题。通过类似于本书这样的努力,Rust 团队希望更多人了解操作系统的概念,特别是编程新手。
+Rust 适用于学生和任何对学习操作系统概念感兴趣的人。通过 Rust,很多人已经了解了像操作系统开发这样的主题。社区非常欢迎并乐于解答学生们的问题。通过类似于本书这样的努力,Rust 团队希望更多人了解操作系统的概念,特别是编程新手。
### 公司
@@ -44,47 +42,58 @@ Rust 适用于希望构建 Rust 编程语言、社区、开发工具和库的开
### 重视速度和稳定性的开发者
-Rust 适用于追求编程语言的速度与稳定性的开发者。所谓速度,我们指你用 Rust 开发出的程序运行速度,以及 Rust 提供的程序开发速度。Rust 的编译器检查确保了增加功能和重构代码时的稳定性。这与缺少这些检查的语言形成鲜明对比,开发者通常害怕修改那些脆弱的遗留代码。通过力求零开销抽象(zero-cost abstractions),高层级的特性被编译为底层代码,与手写的一样快,Rust 致力于使安全的代码也同样快速。
+Rust 适用于追求编程语言的速度与稳定性的开发者。所谓速度,是指你用 Rust 开发出的程序运行速度,以及 Rust 提供的程序开发速度。Rust 的编译器检查确保了增加功能和重构代码时的稳定性。这与缺少这些检查的语言形成鲜明对比,开发者通常害怕修改那些脆弱的遗留代码。通过力求零开销抽象(zero-cost abstractions),高层级的特性被编译为与手写一样快的底层代码,Rust 致力于使安全的代码也同样快速。
-Rust 语言希望能支持更多用户,这里提及的只是最大的利益相关者。总的来讲,Rust 最重要的目标是消除数十年来程序员不得不做的权衡:安全 **与** 生产力、速度 **与** 工程学。请尝试 Rust,看看它的选择是否适合你。
+Rust 语言也希望能支持很多其他用户,这里提及的只是最大的利益相关者。总的来讲,Rust 最重要的目标是消除数十年来程序员不得不做的权衡:安全 **与** 生产力、速度 **与** 工程学。请尝试 Rust,看看这个选择是否适合你。
-## 谁会阅读本书
+## 本书是写给谁的
本书假设你已经使用其他编程语言编写过代码,但并不假设你使用的是何种语言。我们尝试使这些材料能广泛的适用于来自很多不同编程背景的开发者。我们不会花费很多时间讨论编程 **是** 什么或者如何理解它。如果编程对于你来说是完全陌生的,你最好先阅读专门介绍编程的书籍。
## 如何阅读本书
-总体来说,本书假设你会从头到尾顺序阅读。稍后的章节建立在之前章节概念的基础上,同时之前的章节可能不会深入讨论一个主题的细节;通常稍后的章节会重新讨论这些主题。
+总体来说,本书假设你会从头到尾顺序阅读。稍后的章节建立在之前章节概念的基础上,同时之前的章节可能不会深入讨论某个主题的细节;通常稍后的章节会重新讨论这些主题。
你会在本书中发现两类章节:概念章节和项目章节。在概念章节中,我们学习 Rust 的某个方面。在项目章节中,我们应用目前所学的知识一同构建小的程序。第二、十二和二十章是项目章节;其余都是概念章节。
-第一章介绍如何安装 Rust,如何编写 Hello world 程序,以及如何使用 Rust 的包管理器和构建工具 Cargo。第二章是 Rust 语言的实战介绍。我们会介绍一些高层级的概念,在稍后章节会详细介绍。如果你希望立刻就动手实践一下,第二章正好适合你。开始阅读时,你甚至可能希望略过第三章,它介绍了 Rust 中类似其他编程语言中的功能,并直接阅读第四章学习 Rust 的所有权系统。然而,如果你是特别重视细节的学习者,并倾向于在继续之前学习每一个细节,你可能希望略过第二章并直接阅读第三章,并在想要构建项目来实践这些细节时再回来阅读第二章。
+第一章介绍如何安装 Rust,如何编写 Hello, world! 程序,以及如何使用 Rust 的包管理器和构建工具 Cargo。第二章是 Rust 语言的实战介绍。我们会站在较高的层次介绍一些的概念,在稍后的章节种会做详细介绍。如果你希望立刻就动手实践一下,第二章正好适合你。开始阅读时,你甚至可能希望略过第三章,它介绍了 Rust 中类似其他编程语言中的功能,并直接阅读第四章学习 Rust 的所有权系统。然而,如果你是特别重视细节的学习者,并倾向于在继续之前学习每一个细节,你可能希望略过第二章并直接阅读第三章,并在想要构建项目来实践这些细节时再回来阅读第二章。
第五章讨论结构体和方法,第六章介绍枚举、`match` 表达式和 `if let` 控制流结构。在 Rust 中,你将使用结构体和枚举创建自定义类型。
-第七章,你会学习 Rust 的模块系统和私有性规则来组织代码和公有应用程序设计接口(Application Programming Interface, API)。第八章讨论了一些标准库提供的通用集合数据结构,比如 vector、字符串和哈希 map。第九章探索了 Rust 的错误处理哲学和技术。
+第七章,你会学习 Rust 的模块系统和私有性规则来组织代码和公有应用程序接口(Application Programming Interface, API)。第八章讨论了一些标准库提供的通用集合数据结构,比如 vector、字符串和哈希 map。第九章探索了 Rust 的错误处理哲学和技术。
-第十章深入介绍泛型、trait 和生命周期,他们提供了定义出适用于多种类型的代码的能力。第十一章介绍测试,即使 Rust 有安全保证,也需要测试确保程序逻辑正确。第十二章,我们构建了属于自己的在文件中搜索文本的命令行工具 `grep` 的子集功能实现。为此会利用之前章节讨论的很多概念。
+第十章深入介绍泛型、trait 和生命周期,他们提供了定义出适用于多种类型的代码的能力。第十一章全部关于测试,即使 Rust 有安全保证,也需要测试确保程序逻辑正确。第十二章,我们构建了属于自己的在文件中搜索文本的命令行工具 `grep` 的子集功能实现。为此会利用之前章节讨论的很多概念。
第十三章探索了闭包和迭代器:Rust 中来自函数式编程语言的功能。第十四章会更深层次的理解 Cargo 并讨论向他人分享库的最佳实践。第十五章讨论标准库提供的智能指针以及启用这些功能的 trait。
第十六章会学习不同的并发编程模型,并讨论 Rust 如何助你无畏的编写多线程程序。第十七章着眼于比较 Rust 风格与你可能熟悉的面向对象编程原则。
-第十八章是一个模式与模式匹配的参考章节,他们是在整个 Rust 程序中表达意图的强大方式。第十九章是一个高级主题大杂烩,包括 unsafe Rust 和更多关于生命周期、 trait、类型、函数和闭包的内容。
+第十八章是一个模式与模式匹配的参考章节,他们是在整个 Rust 程序中表达意图的强大方式。第十九章是一个高级主题大杂烩,包括 unsafe Rust、宏和更多关于生命周期、 trait、类型、函数和闭包的内容。
+
+第二十章将会完成一个项目,我们会实现一个底层的、多线程的 web server!
+
+最后是一些附录,包含了一些关于语言的参考风格格式的实用信息。附录 A 介绍了 Rust 的关键字。附录 B 介绍 Rust 的运算符和符号。附录 C 介绍标准库提供的派生 trait。附录 D 涉及了一些有用的开发工具,附录 E 介绍了 Rust 的不同版本。
+
+怎样阅读本书都不会有任何问题:如果你希望略过一些内容,请继续!如果你发现疑惑可能会再跳回之前的章节。请随意阅读。
-第二十章会完成一个项目,实现了一个底层的、多线程的 web server!
+
-最后是一些附录,包含了一些关于语言的参考风格格式的实用信息。附录 A 介绍了 Rust 的关键字。附录 B 介绍 Rust 的运算符和符号。附录 C 介绍标准库提供的派生 trait。附录 D 介绍宏。
+学习 Rust 的过程中一个重要的部分是学习如何阅读编译器提供的错误信息:它们会指导你编写出能工作的代码。为此,我们会提供很多不能编译的示例代码,以及各个情况下编译器会展示的错误信息。请注意如果随便输入并运行随机的示例代码,它们可能无法编译!请确保阅读任何你尝试运行的示例周围的内容,检视他们是否有意写错。Ferris 也会帮助你区别那些有意无法工作的代码:
-怎样阅读本书都不会有任何问题:如果你希望略过一些内容,请继续!如果你发现疑惑可能会再跳回之前的章节。无论怎样都是可以的。
+| Ferris | 意义 |
+|------------------------------------------------------------------------|--------------------------------------------------|
+|
| 这些代码不能编译! |
+|
| 这些代码会 panic! |
+|
| 这些代码块包含不安全(unsafe)代码。 |
+|
| 这些代码不会产生期望的行为。 |
-学习 Rust 的过程中一个重要的部分是学习如何阅读编译器提供的错误信息:它们会指导你编写出能工作的代码。为此,我们会提供很多不能编译的示例代码,以及各个情况下编译器会展示的错误信息。请注意如果随便输入并运行随机的示例代码,它们可能无法编译!请确保阅读任何你尝试运行的示例周围的内容,检视他们是否有意写错。在大部分情况,我们会指引你将任何不能编译的代码纠正为正确版本。
+在大部分情况,我们会指引你将任何不能编译的代码纠正为正确版本。
## 源代码
生成本书的源码可以在 [GitHub][book] 上找到。
-[book]: https://github.com/rust-lang/book/tree/master/second-edition/src
+[book]: https://github.com/rust-lang/book/tree/master/src
> 译者注:本译本的 [GitHub 仓库][trpl-zh-cn],欢迎 Issue 和 PR :)
diff --git a/src/ch01-00-getting-started.md b/src/ch01-00-getting-started.md
index c898517..aa0c4cb 100644
--- a/src/ch01-00-getting-started.md
+++ b/src/ch01-00-getting-started.md
@@ -1,10 +1,10 @@
# 入门指南
-> [ch01-00-getting-started.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch01-00-getting-started.md)
+> [ch01-00-getting-started.md](https://github.com/rust-lang/book/blob/master/src/ch01-00-getting-started.md)
>
-> commit 7480e811ab5ad8d53a5b854d9b0c7a5a4f58499f
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
-让我们开始 Rust 之旅!有很多内容要学,但每次旅程总有起点。在本章中,我们会讨论:
+让我们开始 Rust 之旅!有很多内容需要学习,但每次旅程总有起点。在本章中,我们会讨论:
* 在 Linux、macOS 和 Windows 上安装 Rust
* 编写一个打印 `Hello, world!` 的程序
diff --git a/src/ch01-01-installation.md b/src/ch01-01-installation.md
index 653045a..e93f773 100644
--- a/src/ch01-01-installation.md
+++ b/src/ch01-01-installation.md
@@ -1,18 +1,18 @@
## 安装
-> [ch01-01-installation.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch01-01-installation.md)
+> [ch01-01-installation.md](https://github.com/rust-lang/book/blob/master/src/ch01-01-installation.md)
>
-> commit 7480e811ab5ad8d53a5b854d9b0c7a5a4f58499f
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
第一步是安装 Rust。我们通过 `rustup` 下载 Rust,这是一个管理 Rust 版本和相关工具的命令行工具。下载时需要联网。
-> 注意:如果出于某些理由你倾向于不使用 `rustup`,请到 [Rust 安装页面](https://www.rust-lang.org/install.html) 查看其它安装选项。
+> 注意:如果你出于某些理由倾向于不使用 `rustup`,请到 [Rust 安装页面](https://www.rust-lang.org/install.html) 查看其它安装选项。
-接下来的步骤会安装最新的稳定版 Rust 编译器。本书所有示例和输出采用稳定版 Rust 1.21.0。Rust 的稳定性确保本书所有示例在最新版本的 Rust 中能够继续编译。不同版本的输出可能略有不同,因为 Rust 经常改进错误信息和警告。也就是说,通过这些步骤安装的最新稳定版 Rust,能正常运行本书中的内容。
+接下来的步骤会安装最新的稳定版 Rust 编译器。Rust 的稳定性确保本书所有示例在最新版本的 Rust 中能够继续编译。不同版本的输出可能略有不同,因为 Rust 经常改进错误信息和警告。也就是说,任何通过这些步骤安装的最新稳定版 Rust,都应该能正常运行本书中的内容。
> ### 命令行标记
>
-> 本章和全书中,我们会展示在终端中使用的命令。所有需要输入到终端的行都以 `$` 开头。但无需输入`$`;它代表每行命令的起点。不以 `$` 起始的行通常展示之前命令的输出。另外,PowerShell 专用的示例会采用 `>` 而不是 `$`。
+> 本章和全书中,我们会展示一些在终端中使用的命令。所有需要输入到终端的行都以 `$` 开头。但无需输入`$`;它代表每行命令的起点。不以 `$` 起始的行通常展示之前命令的输出。另外,PowerShell 专用的示例会采用 `>` 而不是 `$`。
### 在 Linux 或 macOS 上安装 `rustup`
@@ -46,12 +46,12 @@ $ export PATH="$HOME/.cargo/bin:$PATH"
### 在 Windows 上安装 `rustup`
-在 Windows 上,前往 [https://www.rust-lang.org/install.html][install] 并按照说明安装 Rust。在安装过程的某个步骤,你会收到一个信息说明为什么需要安装 Visual Studio 2013 或之后版本的 C++ build tools。获取这些 build tools 最方便的方法是安装 [Build Tools for Visual Studio 2017][visualstudio]。这个工具在 “Other Tools and Frameworks” 部分。
+在 Windows 上,前往 [https://www.rust-lang.org/install.html][install] 并按照说明安装 Rust。在安装过程的某个步骤,你会收到一个信息说明为什么需要安装 Visual Studio 2013 或更新版本的 C++ build tools。获取这些 build tools 最方便的方法是安装 [Build Tools for Visual Studio 2017][visualstudio]。这个工具在 “Other Tools and Frameworks” 部分。
[install]: https://www.rust-lang.org/install.html
[visualstudio]: https://www.visualstudio.com/downloads/
-本书的余下部分,使用能同时运行于 *cmd.exe* 和 PowerShell 的命令。如果存在特定差异,我们会解释使用哪一个。
+本书的余下部分会使用能同时运行于 *cmd.exe* 和 PowerShell 的命令。如果存在特定差异,我们会解释使用哪一个。
### 更新和卸载
@@ -88,10 +88,10 @@ rustc x.y.z (abcabcabc yyyy-mm-dd)
[users]: https://users.rust-lang.org/
[stackoverflow]: http://stackoverflow.com/questions/tagged/rust
-恭喜入坑!(此处应该有掌声!)
+> 译者:恭喜入坑!(此处应该有掌声!)
### 本地文档
安装程序也自带一份文档的本地拷贝,可以离线阅读。运行 `rustup doc` 在浏览器中查看本地文档。
-任何时候,如果你拿不准标准库中的类型或函数的用途和用法,请查看应用程序接口(application programming interface,API)文档!
\ No newline at end of file
+任何时候,如果你拿不准标准库中的类型或函数的用途和用法,请查阅应用程序接口(application programming interface,API)文档!
\ No newline at end of file
diff --git a/src/ch01-02-hello-world.md b/src/ch01-02-hello-world.md
index ac166b9..482afe3 100644
--- a/src/ch01-02-hello-world.md
+++ b/src/ch01-02-hello-world.md
@@ -1,10 +1,10 @@
## Hello, World!
-> [ch01-02-hello-world.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch01-02-hello-world.md)
+> [ch01-02-hello-world.md](https://github.com/rust-lang/book/blob/master/src/ch01-02-hello-world.md)
>
-> commit 5dfa983aa8fca89f8b70cafe58ab8417491d2018
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
-既然安装好了 Rust,我们来编写第一个 Rust 程序。当学习一门新语言的时候,使用该语言在屏幕上打印 `Hello, world!` 是一项传统,这里我们将沿用这个传统!
+既然安装好了 Rust,我们来编写第一个 Rust 程序。当学习一门新语言的时候,使用该语言在屏幕上打印 `Hello, world!` 是一项传统,我们将沿用这一传统!
> 注意:本书假设你熟悉基本的命令行操作。Rust 对于你的编辑器、工具,以及代码位于何处并没有特定的要求,如果你更倾向于使用集成开发环境(IDE),而不是命令行,请尽管使用你喜欢的 IDE。目前很多 IDE 已经不同程度的支持 Rust;查看 IDE 文档了解更多细节。最近,Rust 团队已经致力于提供强大的 IDE 支持,而且进展飞速!
@@ -89,7 +89,7 @@ fn main() {
这几行定义了一个 Rust 函数。`main` 函数是一个特殊的函数:在可执行的 Rust 程序中,它总是最先运行的代码。第一行代码声明了一个叫做 `main` 的函数,它没有参数也没有返回值。如果有参数的话,它们的名称应该出现在小括号中,`()`。
-还须注意,函数体被包裹在花括号中,`{}`。Rust 要求所有函数体都要用花括号包裹起来(译者注:有些语言,当函数体只有一行时可以省略花括号,但在 Rust 中是不行的)。一般来说,将左花括号与函数声明置于同一行并以空格分隔,是良好的代码风格。
+还须注意,函数体被包裹在花括号中,`{}`。Rust 要求所有函数体都要用花括号包裹起来。一般来说,将左花括号与函数声明置于同一行并以空格分隔,是良好的代码风格。
在编写本书的时候,一个叫做 `rustfmt` 的自动格式化工具正在开发中。如果你希望在 Rust 项目中保持一种标准风格,`rustfmt` 会将代码格式化为特定的风格。Rust 团队计划最终将该工具包含在标准 Rust 发行版中,就像 `rustc`。所以根据你阅读本书的时间,它可能已经安装到你的电脑中了!检查在线文档以了解更多细节。
@@ -101,7 +101,7 @@ fn main() {
这行代码完成这个简单程序的所有工作:在屏幕上打印文本。这里有四个重要的细节需要注意。首先 Rust 的缩进风格使用 4 个空格,而不是 1 个制表符(tab)。
-第二,`println!` 调用了一个 Rust 宏(macro)。如果是调用函数,则应输入 `println`(没有`!`)。我们将在附录 D 中详细讨论宏。现在你只需记住,当看到符号 `!` 的时候,就意味着调用的是宏而不是普通函数。
+第二,`println!` 调用了一个 Rust 宏(macro)。如果是调用函数,则应输入 `println`(没有`!`)。我们将在第十九章详细讨论宏。现在你只需记住,当看到符号 `!` 的时候,就意味着调用的是宏而不是普通函数。
第三,`"Hello, world!"` 是一个字符串。我们把这个字符串作为一个参数传递给 `println!`,字符串将被打印到屏幕上。
@@ -119,17 +119,26 @@ $ rustc main.rs
如果你有 C 或 C++ 背景,就会发现这与 `gcc` 和 `clang` 类似。编译成功后,Rust 会输出一个二进制的可执行文件。
-在 Linux、macOS 或 Windows 的 PowerShell 上,在 shell 中输入 `ls` 命令就可看见这个可执行文件,如下:
+在 Linux、macOS 或 Windows 的 PowerShell 上,在 shell 中输入 `ls` 命令也可以看见这个可执行文件,如下:
```text
-$ ls
-main main.rs
+> ls
+
+
+ Directory: Path:\to\the\project
+
+
+Mode LastWriteTime Length Name
+---- ------------- ------ ----
+-a---- 6/1/2018 7:31 AM 137728 main.exe
+-a---- 6/1/2018 7:31 AM 1454080 main.pdb
+-a---- 6/1/2018 7:31 AM 14 main.rs
```
在 Windows 的 CMD 上,则输入如下内容:
```cmd
-> dir /B %= the /B option says to only show the file names =%
+> dir /B %= /B 参数表示只显示文件名 =%
main.exe
main.pdb
main.rs
@@ -138,7 +147,7 @@ main.rs
这展示了扩展名为 *.rs* 的源文件、可执行文件(在 Windows 下是 *main.exe*,其它平台是 *main*),以及当使用 CMD 时会有一个包含调试信息、扩展名为 *.pdb* 的文件。从这里开始运行 *main* 或 *main.exe* 文件,如下:
```text
-$ ./main # or .\main.exe on Windows
+$ ./main # Windows 是 .\main.exe
```
如果 *main.rs* 是上文所述的 Hello, world! 程序,它将会在终端上打印 `Hello, world!`。
diff --git a/src/ch01-03-hello-cargo.md b/src/ch01-03-hello-cargo.md
index 0acdb0c..7904ebc 100644
--- a/src/ch01-03-hello-cargo.md
+++ b/src/ch01-03-hello-cargo.md
@@ -1,14 +1,14 @@
## Hello, Cargo!
-> [ch01-03-hello-cargo.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch01-03-hello-cargo.md)
+> [ch01-03-hello-cargo.md](https://github.com/rust-lang/book/blob/master/src/ch01-03-hello-cargo.md)
>
-> commit 7480e811ab5ad8d53a5b854d9b0c7a5a4f58499f
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
Cargo 是 Rust 的构建系统和包管理器。大多数 Rustacean 们使用 Cargo 来管理他们的 Rust 项目,因为它可以为你处理很多任务,比如构建代码、下载依赖库并编译这些库。(我们把代码所需要的库叫做 **依赖**(*dependencies*))。
-最简单的 Rust 程序,比如我们刚刚编写的,没有任何依赖。所以如果使用 Cargo 来构建 Hello, world! 项目,将只会用到 Cargo 的构建代码那部分功能。随着编写的 Rust 程序更加复杂,你会添加依赖,如果你一开始就使用 Cargo 的话,添加依赖将会变得简单许多。
+最简单的 Rust 程序,比如我们刚刚编写的,没有任何依赖。所以如果使用 Cargo 来构建 Hello, world! 项目,将只会用到 Cargo 的构建代码那部分功能。如果编写更为复杂的 Rust 程序,你会添加依赖,这样如果你一开始就使用 Cargo 的话,添加依赖将会变得简单许多。
-由于绝大多数 Rust 项目使用 Cargo,本书接下来的部分假设你也使用 Cargo。如果使用 “安装” 章节介绍的官方安装包的话,则自带了 Cargo。如果通过其他方式安装的话,可以在终端输入如下命令检查是否安装了 Cargo:
+由于绝大多数 Rust 项目使用 Cargo,本书接下来的部分假设你也使用 Cargo。如果使用 “安装” 部分介绍的官方安装包的话,则自带了 Cargo。如果通过其他方式安装的话,可以在终端输入如下命令检查是否安装了 Cargo:
```text
$ cargo --version
@@ -21,17 +21,17 @@ $ cargo --version
我们使用 Cargo 创建一个新项目,然后看看与上面的 Hello, world! 项目有什么不同。回到 *projects* 目录(或者你存放代码的目录)。接着,可在任何操作系统下运行以下命令:
```text
-$ cargo new hello_cargo --bin
+$ cargo new hello_cargo
$ cd hello_cargo
```
-第一行命令新建了名为 *hello_cargo* 的二进制可执行程序。为 `cargo new` 传入 `--bin` 参数会生成一个可执行程序(通常就叫做 **二进制文件**,*binary*),而不是一个库。项目被命名为 `hello_cargo`,同时 Cargo 在一个同名目录中创建项目文件。
+第一行命令新建了名为 *hello_cargo* 的目录。我们将项目命名为 *hello_cargo*,同时 Cargo 在一个同名目录中创建项目文件。
进入 *hello_cargo* 目录并列出文件。将会看到 Cargo 生成了两个文件和一个目录:一个 *Cargo.toml* 文件,一个 *src* 目录,以及位于 *src* 目录中 *main.rs* 文件。它也在 *hello_cargo* 目录初始化了一个 git 仓库,以及一个 *.gitignore* 文件。
> 注意:Git 是一个常用的版本控制系统(version control system, VCS)。可以通过 `--vcs` 参数使 `cargo new` 切换到其它版本控制系统(VCS),或者不使用 VCS。运行 `cargo new --help` 参看可用的选项。
-请选用文本编辑器打开 *Cargo.toml* 文件。它应该看起来如示例 1-2 所示:
+请自行选用文本编辑器打开 *Cargo.toml* 文件。它应该看起来如示例 1-2 所示:
文件名: Cargo.toml
@@ -40,6 +40,7 @@ $ cd hello_cargo
name = "hello_cargo"
version = "0.1.0"
authors = ["Your Name "]
+edition = "2018"
[dependencies]
```
@@ -52,9 +53,9 @@ authors = ["Your Name "]
第一行,`[package]`,是一个片段(section)标题,表明下面的语句用来配置一个包。随着我们在这个文件增加更多的信息,还将增加其他片段(section)。
-接下来的三行设置了 Cargo 编译程序所需的配置:项目的名称、版本和作者。Cargo 从环境中获取你的名字和 email 信息,所以如果这些信息不正确,请修改并保存此文件。
+接下来的四行设置了 Cargo 编译程序所需的配置:项目的名称、版本和作者。Cargo 从环境中获取你的名字和 email 信息,所以如果这些信息不正确,请修改并保存此文件。附录 E 会介绍 `edition` 的值。
-最后一行,`[dependencies]`,是罗列项目依赖的片段。在 Rust 中,代码包被称为 *crates*。这个项目并不需要其他的 crate,不过在第二章的第一个项目会用到依赖,那时会用得上这个片段。
+最后一行,`[dependencies]`,是罗列项目依赖的片段的开始。在 Rust 中,代码包被称为 *crates*。这个项目并不需要其他的 crate,不过在第二章的第一个项目会用到依赖,那时会用得上这个片段。
现在打开 *src/main.rs* 看看:
@@ -85,7 +86,7 @@ $ cargo build
这个命令会创建一个可执行文件 *target/debug/hello_cargo* (在 Windows 上是 *target\debug\hello_cargo.exe*),而不是放在目前目录下。可以通过这个命令运行可执行文件:
```text
-$ ./target/debug/hello_cargo # or .\target\debug\hello_cargo.exe on Windows
+$ ./target/debug/hello_cargo # 或者在 Windows 下为 .\target\debug\hello_cargo.exe
Hello, world!
```
@@ -109,15 +110,16 @@ $ cargo run
Running `target/debug/hello_cargo`
Hello, world!
```
+
Cargo 还提供了一个叫 `cargo check` 的命令。该命令快速检查代码确保其可以编译,但并不产生可执行文件:
```text
$ cargo check
- Compiling hello_cargo v0.1.0 (file:///projects/hello_cargo)
+ Checking hello_cargo v0.1.0 (file:///projects/hello_cargo)
Finished dev [unoptimized + debuginfo] target(s) in 0.32 secs
```
-为什么你会不需要可执行文件呢?通常 `cargo check` 要比 `cargo build` 快得多,因为它省略了生成可执行文件的步骤。如果编写代码时持续的进行检查,`cargo check` 会加速开发!为此很多 Rustaceans 编写代码时定期运行 `cargo check` 确保它们可以编译。当准备好使用可执行文件时才运行 `cargo build`。
+为什么你会不需要可执行文件呢?通常 `cargo check` 要比 `cargo build` 快得多,因为它省略了生成可执行文件的步骤。如果你在编写代码时持续的进行检查,`cargo check` 会加速开发!为此很多 Rustaceans 编写代码时定期运行 `cargo check` 确保它们可以编译。当准备好使用可执行文件时才运行 `cargo build`。
我们回顾下已学习的 Cargo 内容:
@@ -125,7 +127,7 @@ $ cargo check
* 可以使用 `cargo run` 一步构建并运行项目。
* 有别于将构建结果放在与源码相同的目录,Cargo 会将其放到 *target/debug* 目录。
-使用 Cargo 的一个额外的优点是,不管你使用什么操作系统,其命令都是一样的。所以从此以后本书将不再为 Linux 和 macOS 以及 Windows 提供相应的命令。
+使用 Cargo 的一个额外的优点是,不管你使用什么操作系统,其命令都是一样的。所以从现在开始本书将不再为 Linux 和 macOS 以及 Windows 提供相应的命令。
### 发布(release)构建
@@ -149,7 +151,7 @@ $ cargo build
## 总结
-你已经踏上了 Rust 之旅!在本章中,你学习了如何:
+你已经准备好开启 Rust 之旅了!在本章中,你学习了如何:
* 使用 `rustup` 安装最新稳定版的 Rust
* 更新到新版的 Rust
@@ -157,4 +159,4 @@ $ cargo build
* 直接通过 `rustc` 编写并运行 Hello, world! 程序
* 使用 Cargo 创建并运行新项目
-是时候通过构建更真实的程序来熟悉读写 Rust 代码了。所以在下一章,我们会构建一个猜猜看游戏程序。如果你更愿意从学习 Rust 常用的编程概念开始,请阅读第三章,接着再回到第二章。
+是时候通过构建更实质性的程序来熟悉读写 Rust 代码了。所以在第二章我们会构建一个猜猜看游戏程序。如果你更愿意从学习 Rust 常用的编程概念开始,请阅读第三章,接着再回到第二章。
diff --git a/src/ch02-00-guessing-game-tutorial.md b/src/ch02-00-guessing-game-tutorial.md
index 61d0a03..4ce9676 100644
--- a/src/ch02-00-guessing-game-tutorial.md
+++ b/src/ch02-00-guessing-game-tutorial.md
@@ -1,8 +1,8 @@
# 编写 猜猜看 游戏
-> [ch02-00-guessing-game-tutorial.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch02-00-guessing-game-tutorial.md)
+> [ch02-00-guessing-game-tutorial.md](https://github.com/rust-lang/book/blob/master/src/ch02-00-guessing-game-tutorial.md)
>
-> commit 7480e811ab5ad8d53a5b854d9b0c7a5a4f58499f
+> commit a86c1d315789b3ca13b20d50ad5005c62bdd9e37
让我们一起动手完成一个项目,来快速上手 Rust!本章将介绍 Rust 中一些常用概念,并通过真实的程序来展示如何运用它们。你将会学到 `let`、`match`、方法、关联函数、外部 crate 等知识!后续章节会深入探讨这些概念的细节。在这一章,我们将做基础练习。
@@ -13,11 +13,11 @@
要创建一个新项目,进入第一章中创建的 *projects* 目录,使用 Cargo 新建一个项目,如下:
```text
-$ cargo new guessing_game --bin
+$ cargo new guessing_game
$ cd guessing_game
```
-第一个命令,`cargo new`,它获取项目的名称(`guessing_game`)作为第一个参数。`--bin` 参数告诉 Cargo 创建一个二进制项目,与第一章类似。第二个命令进入到新创建的项目目录。
+第一个命令,`cargo new`,它获取项目的名称(`guessing_game`)作为第一个参数。第二个命令进入到新创建的项目目录。
看看生成的 *Cargo.toml* 文件:
@@ -119,7 +119,7 @@ println!("Please input your guess.");
let mut guess = String::new();
```
-现在程序开始变得有意思了!这一小行代码发生了很多事。注意这是一个 `let` 语句,用来创建 **变量**。这里是另外一个例子:
+现在程序开始变得有意思了!这一小行代码发生了很多事。注意这是一个 `let` 语句,用来创建 **变量**(*variable*)。这里是另外一个例子:
```rust,ignore
let foo = bar;
@@ -128,11 +128,11 @@ let foo = bar;
这行代码新建了一个叫做 `foo` 的变量并把它绑定到值 `bar` 上。在 Rust 中,变量默认是不可变的。我们将会在第三章的 “变量与可变性” 部分详细讨论这个概念。下面的例子展示了如何在变量名前使用 `mut` 来使一个变量可变:
```rust,ignore
-let foo = 5; // immutable
-let mut bar = 5; // mutable
+let foo = 5; // 不可变
+let mut bar = 5; // 可变
```
-> 注意:`//` 语法开始一个注释,持续到行尾。Rust 忽略注释中的所有内容,将在第三章中详细介绍注释。
+> 注意:`//` 语法开始一个注释,持续到行尾。Rust 忽略注释中的所有内容,第三章将会详细介绍注释。
让我们回到猜猜看程序中。现在我们知道了 `let mut guess` 会引入一个叫做 `guess` 的可变变量。等号(`=`)的右边是 `guess` 所绑定的值,它是 `String::new` 的结果,这个函数会返回一个 `String` 的新实例。[`String`][string] 是一个标准库提供的字符串类型,它是 UTF-8 编码的可增长文本块。
@@ -214,7 +214,7 @@ Rust 警告我们没有使用 `read_line` 的返回值 `Result`,说明有一
### 使用 `println!` 占位符打印值
-除了位于结尾的大括号,目前为止就只有一行代码值得讨论一下了,就是这一行:
+除了位于结尾的大括号,目前为止就只有这一行代码值得讨论一下了,就是这一行:
```rust,ignore
println!("You guessed: {}", guess);
@@ -295,9 +295,9 @@ $ cargo build
在更新完 registry 后,Cargo 检查 `[dependencies]` 片段并下载缺失的 crate 。本例中,虽然只声明了 `rand` 一个依赖,然而 Cargo 还是额外获取了 `libc` 的拷贝,因为 `rand` 依赖 `libc` 来正常工作。下载完成后,Rust 编译依赖,然后使用这些依赖编译项目。
-如果不做任何修改,立刻再次运行 `cargo build`,则不会看到任何除了 `Finished` 完成提示之外的输出。Cargo 知道它已经下载并编译了依赖,同时 *Cargo.toml* 文件也没有变动。Cargo 还知道代码也没有任何修改,所以它不会重新编译代码。因为无事可做,它简单的退出了。
+如果不做任何修改,立刻再次运行 `cargo build`,则不会看到任何除了 `Finished` 行之外的输出。Cargo 知道它已经下载并编译了依赖,同时 *Cargo.toml* 文件也没有变动。Cargo 还知道代码也没有任何修改,所以它不会重新编译代码。因为无事可做,它简单的退出了。
-如果打开 *src/main.rs* 文件,做一些无关紧要的修改,保存并再次构建,只会出现两行输出:
+如果打开 *src/main.rs* 文件,做一些无关紧要的修改,保存并再次构建,则会出现两行输出:
```text
$ cargo build
@@ -311,7 +311,7 @@ $ cargo build
Cargo 有一个机制来确保任何人在任何时候重新构建代码,都会产生相同的结果:Cargo 只会使用你指定的依赖版本,除非你又手动指定了别的。例如,如果下周 `rand` crate 的 `0.3.15` 版本出来了,它修复了一个重要的 bug,同时也含有一个会破坏代码运行的缺陷,这时会发生什么呢?
-这个问题的答案是 *Cargo.lock* 文件。它在第一次运行 `cargo build` 时创建,并放在 *guessing_game* 目录。当第一次构建项目时,Cargo 计算出所有符合要求的依赖版本并写入 *Cargo.lock* 文件。当将来构建项目时,Cargo 会发现 *Cargo.lock* 已存在并使用其中指定的版本,而不是再次计算所有的版本。这使得你拥有了一个自动化的可重现的构建。换句话说,项目会持续使用 `0.3.14` 直到你显式升级,感谢 *Cargo.lock* 文件。
+这个问题的答案是 *Cargo.lock* 文件。它在第一次运行 `cargo build` 时创建,并放在 *guessing_game* 目录。当第一次构建项目时,Cargo 计算出所有符合要求的依赖版本并写入 *Cargo.lock* 文件。当将来构建项目时,Cargo 会发现 *Cargo.lock* 已存在并使用其中指定的版本,而不是再次计算所有的版本。这使得你拥有了一个自动化的可重现的构建。换句话说,项目会持续使用 `0.3.14` 直到你显式升级,多亏有了 *Cargo.lock* 文件。
#### 更新 crate 到一个新版本
@@ -337,20 +337,18 @@ rand = "0.4.0"
下一次运行 `cargo build` 时,Cargo 会从 registry 更新可用的 crate,并根据你指定的新版本重新计算。
-第十四章会讲到 [Cargo][doccargo] 及其[生态系统][doccratesio]的更多内容,不过目前你只需要了解这么多。通过 Cargo 复用库文件非常容易,因此 Rustacean 能够编写出由很多包组装而成的更轻巧的项目。
+第十四章会讲到 [Cargo][doccargo] 及其[生态系统][doccratesio] 的更多内容,不过目前你只需要了解这么多。通过 Cargo 复用库文件非常容易,因此 Rustacean 能够编写出由很多包组装而成的更轻巧的项目。
[doccargo]: http://doc.crates.io
[doccratesio]: http://doc.crates.io/crates-io.html
### 生成一个随机数
-你已经把 `rand` crate 添加到 *Cargo.toml* 了,让我们开始 **使用** `rand` 吧。下一步是更新 *src/main.rs*,如示例 2-3 所示:
+你已经把 `rand` crate 添加到 *Cargo.toml* 了,让我们开始 **使用** `rand` 吧。下一步是更新 *src/main.rs*,如示例 2-3 所示。
文件名: src/main.rs
```rust,ignore
-extern crate rand;
-
use std::io;
use rand::Rng;
@@ -374,15 +372,15 @@ fn main() {
示例 2-3:添加生成随机数的代码
-首先,这里在顶部增加一行 `extern crate rand;` 通知 Rust 我们要使用外部依赖 `rand`。这也会调用相应的 `use rand`,所以现在可以使用 `rand::` 前缀来调用 `rand` crate 中的任何内容。
+首先,这里新增了一行通知 Rust 我们要使用外部依赖 `rand`。这等同于调用 `use rand`,所以现在可以使用 `rand::` 前缀来调用 `rand` crate 中的任何内容。
接下来增加了另一行 `use`:`use rand::Rng`。`Rng` 是一个 trait,它定义了随机数生成器应实现的方法,想使用这些方法的话,此 trait 必须在作用域中。第十章会详细介绍 trait。
-另外,中间还新增加了两行。`rand::thread_rng` 函数提供实际使用的随机数生成器:它位于当前执行线程本地,并从操作系统获取 seed。接下来,调用随机数生成器的 `gen_range` 方法。这个方法由刚才引入到作用域的 `Rng` trait 定义。`gen_range` 方法获取两个数字作为参数,并生成一个范围在两者之间的随机数。它包含下限但不包含上限,所以需要指定 `1` 和 `101` 来请求一个 1 和 100 之间的数。
+另外,中间还新增加了两行。`rand::thread_rng` 函数提供实际使用的随机数生成器:它位于当前执行线程的本地环境中,并从操作系统获取 seed。接下来,调用随机数生成器的 `gen_range` 方法。这个方法由刚才引入到作用域的 `Rng` trait 定义。`gen_range` 方法获取两个数字作为参数,并生成一个范围在两者之间的随机数。它包含下限但不包含上限,所以需要指定 `1` 和 `101` 来请求一个 1 和 100 之间的数。
> 注意:你不可能凭空就知道应该 use 哪个 trait 以及该从 crate 中调用哪个方法。crate 的使用说明位于其文档中。Cargo 有一个很棒的功能是:运行 `cargo doc --open` 命令来构建所有本地依赖提供的文档,并在浏览器中打开。例如,假设你对 `rand` crate 中的其他功能感兴趣,你可以运行 `cargo doc --open` 并点击左侧导航栏中的 `rand`。
-新增加的第二行代码打印出了秘密数字。这在开发程序时很有用,因为可以测试它,不过在最终版本中会删掉它。游戏一开始就打印出结果就没什么可玩的了!
+新增加的第二行代码打印出了秘密数字。这在开发程序时很有用,因为可以测试它,不过在最终版本中会删掉它。如果游戏一开始就打印出结果就没什么可玩的了!
尝试运行程序几次:
@@ -413,9 +411,7 @@ You guessed: 5
文件名: src/main.rs
-```rust,ignore
-extern crate rand;
-
+```rust,ignore,does_not_compile
use std::io;
use std::cmp::Ordering;
use rand::Rng;
@@ -500,13 +496,13 @@ let guess: u32 = guess.trim().parse()
这里创建了一个叫做 `guess` 的变量。不过等等,不是已经有了一个叫做 `guess` 的变量了吗?确实如此,不过 Rust 允许用一个新值来 **隐藏** (*shadow*) `guess` 之前的值。这个功能常用在需要转换值类型之类的场景。它允许我们复用 `guess` 变量的名字,而不是被迫创建两个不同变量,诸如 `guess_str` 和 `guess` 之类。(第三章会介绍 shadowing 的更多细节。)
-我们将 `guess` 绑定到 `guess.trim().parse()` 表达式上。表达式中的 `guess` 是包含输入的原始 `String` 类型。`String` 实例的 `trim` 方法会去除字符串开头和结尾的空白字符。`u32` 只能由数字字符转换,不过用户必须输入 return 键才能让 `read_line` 返回,然而用户按下 return 键时,会在字符串中增加一个换行(newline)符。例如,用户输入 5 并按下 return,`guess` 看起来像这样:`5\n`。`\n` 代表 “换行”,回车键。`trim` 方法消除 `\n`,只留下`5`。
+我们将 `guess` 绑定到 `guess.trim().parse()` 表达式上。表达式中的 `guess` 是包含输入的原始 `String` 类型。`String` 实例的 `trim` 方法会去除字符串开头和结尾的空白字符。`u32` 只能由数字字符转换,不过用户必须输入 enter 键才能让 `read_line` 返回,然而用户按下 enter 键时,会在字符串中增加一个换行(newline)符。例如,用户输入 5 并按下 enter,`guess` 看起来像这样:`5\n`。`\n` 代表 “换行”,回车键。`trim` 方法消除 `\n`,只留下 `5`。
[字符串的 `parse` 方法][parse] 将字符串解析成数字。因为这个方法可以解析多种数字类型,因此需要告诉 Rust 具体的数字类型,这里通过 `let guess: u32` 指定。`guess` 后面的冒号(`:`)告诉 Rust 我们指定了变量的类型。Rust 有一些内建的数字类型;`u32` 是一个无符号的 32 位整型。对于不大的正整数来说,它是不错的类型,第三章还会讲到其他数字类型。另外,程序中的 `u32` 注解以及与 `secret_number` 的比较,意味着 Rust 会推断出 `secret_number` 也是 `u32` 类型。现在可以使用相同类型比较两个值了!
[parse]: https://doc.rust-lang.org/std/primitive.str.html#method.parse
-`parse` 调用很容易产生错误。例如,字符串中包含 `A👍%`,就无法将其转换为一个数字。因此,`parse` 方法返回一个 `Result` 类型。像之前 “使用 `Result` 类型来处理潜在的错误” 讨论的 `read_line` 方法那样,再次按部就班的用 `expect` 方法处理即可。如果 `parse` 不能从字符串生成一个数字,返回一个 `Result::Err` 时,`expect` 会使游戏崩溃并打印附带的信息。如果 `parse` 成功地将字符串转换为一个数字,它会返回 `Result::Ok`,然后 `expect` 会返回 `Ok` 中的数字。
+`parse` 调用很容易产生错误。例如,字符串中包含 `A👍%`,就无法将其转换为一个数字。因此,`parse` 方法返回一个 `Result` 类型。像之前 “使用 `Result` 类型来处理潜在的错误” 讨论的 `read_line` 方法那样,再次按部就班的用 `expect` 方法处理即可。如果 `parse` 不能从字符串生成一个数字,返回一个 `Result` 的 `Err` 成员时,`expect` 会使游戏崩溃并打印附带的信息。如果 `parse` 成功地将字符串转换为一个数字,它会返回 `Result` 的 `Ok` 成员,然后 `expect` 会返回 `Ok` 值中的数字。
现在让我们运行程序!
@@ -552,7 +548,7 @@ Too big!
}
```
-如上所示,我们将提示用户猜测之后的所有内容放入了循环。确保 loop 循环中的代码多缩进四个空格,再次运行程序。注意这里有一个新问题,因为程序忠实地执行了我们的要求:永远地请求另一个猜测,用户好像没法退出啊!
+如上所示,我们将提示用户猜测之后的所有内容放入了循环。确保 loop 循环中的代码多缩进四个空格,再次运行程序。注意这里有一个新问题,因为程序忠实地执行了我们的要求:永远地请求另一个猜测,用户好像无法退出啊!
用户总能使用 ctrl-c 终止程序。不过还有另一个方法跳出无限循环,就是 “比较猜测与秘密数字” 部分提到的 `parse`:如果用户输入的答案不是一个数字,程序会崩溃。用户可以利用这一点来退出,如下所示:
@@ -666,8 +662,6 @@ You win!
文件名: src/main.rs
```rust,ignore
-extern crate rand;
-
use std::io;
use std::cmp::Ordering;
use rand::Rng;
diff --git a/src/ch03-00-common-programming-concepts.md b/src/ch03-00-common-programming-concepts.md
index 0c5ea69..fa9f844 100644
--- a/src/ch03-00-common-programming-concepts.md
+++ b/src/ch03-00-common-programming-concepts.md
@@ -1,13 +1,41 @@
# 通用编程概念
-> [ch03-00-common-programming-concepts.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch03-00-common-programming-concepts.md)
+> [ch03-00-common-programming-concepts.md](https://github.com/rust-lang/book/blob/master/src/ch03-00-common-programming-concepts.md)
>
-> commit b64de01431cdf1020ad3358d2f83e46af68a39ed
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
本章介绍一些几乎所有编程语言都有的概念,以及它们在 Rust 中是如何工作的。很多编程语言的核心概念都是共通的,本章中展示的概念都不是 Rust 所特有的,不过我们会在 Rust 上下文中讨论它们,并解释使用这些概念的惯例。
具体来说,我们将会学习变量、基本类型、函数、注释和控制流。每一个 Rust 程序中都会用到这些基础知识,提早学习这些概念会让你在起步时就打下坚实的基础。
-> ### 关键字
->
-> Rust 语言有一组保留的 **关键字**(*keywords*),就像大部分语言一样,它们只能由语言本身使用。记住,你不能使用这些关键字作为变量或函数的名称。大部分关键字有特殊的意义,你将在你的 Rust 程序中使用它们完成各种任务;一些关键字目前没有相应的功能,是为将来可能添加的功能保留的。可以在附录 A 中找到关键字的列表。
+## 关键字
+
+Rust 语言有一组保留的 **关键字**(*keywords*),就像大部分语言一样,它们只能由语言本身使用。记住,你不能使用这些关键字作为变量或函数的名称。大部分关键字有特殊的意义,你将在 Rust 程序中使用它们完成各种任务;一些关键字目前没有相应的功能,是为将来可能添加的功能保留的。可以在附录 A 中找到关键字的列表。
+
+## 标识符
+
+这里我们将对本书中的一些概念做一些解释:变量、函数、结构体等等。所有这些都需要名称。Rust 中的名称被称为 “标识符”(“identifier”),它们可以是任意非空的 ASCII 字符串,不过有如下限制:
+
+要么是:
+
+* 第一个字符是字母。
+* 其它字符是字母数字或者 _。
+
+或者是:
+
+* 第一个字符是 _。
+* 标识符需多于一个字符。单独的 _ 不是标识符。
+* 其它字符是字母数字或者 _。
+
+### 原始标识符
+
+有时出于某种原因你可能需要将关键字作为名称。比如你需要调用 C 语言库中名为 *match* 的函数,在 C 语言中 *match* 不是关键字。为此你可以使用 “原始标识符”(“raw identifier”)。原始标识符以 `r#` 开头:
+
+```rust,ignore
+let r#fn = "this variable is named 'fn' even though that's a keyword";
+
+// 调用名为 'match' 的函数
+r#match();
+```
+
+你无需经常用到原始标识符,但是当你 **真正** 需要它们时可以这么做。
\ No newline at end of file
diff --git a/src/ch03-01-variables-and-mutability.md b/src/ch03-01-variables-and-mutability.md
index 68af919..f5ec328 100644
--- a/src/ch03-01-variables-and-mutability.md
+++ b/src/ch03-01-variables-and-mutability.md
@@ -1,18 +1,18 @@
## 变量和可变性
-> [ch03-01-variables-and-mutability.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch03-01-variables-and-mutability.md)
+> [ch03-01-variables-and-mutability.md](https://github.com/rust-lang/book/blob/master/src/ch03-01-variables-and-mutability.md)
>
-> commit f949ff883628db8ed2f2f5f19e146ebf19ed6a6f
+> commit a86c1d315789b3ca13b20d50ad5005c62bdd9e37
-第二章中提到过,变量默认是不可改变的(immutable)。这是推动你以充分利用 Rust 提供的安全性和简单并发性编写代码的众多方式之一。不过,你仍然可以使用可变变量。让我们探讨一下 Rust 拥抱不可变性的原因及方法,以及何时你不想使用不可变性。
+第二章中提到过,变量默认是不可改变的(immutable)。这是推动你以充分利用 Rust 提供的安全性和简单并发性来编写代码的众多方式之一。不过,你仍然可以使用可变变量。让我们探讨一下 Rust 拥抱不可变性的原因及方法,以及何时你不想使用不可变性。
-当变量不可变时,一旦值被绑定一个名称上,你就不能改变这个值。为了对此进行说明,使用 `cargo new --bin variables` 命令在 *projects* 目录生成一个叫做 *variables* 的新项目。
+当变量不可变时,一旦值被绑定一个名称上,你就不能改变这个值。为了对此进行说明,使用 `cargo new variables` 命令在 *projects* 目录生成一个叫做 *variables* 的新项目。
接着,在新建的 *variables* 目录,打开 *src/main.rs* 并将代码替换为如下代码,这些代码还不能编译:
文件名: src/main.rs
-```rust,ignore
+```rust,ignore,does_not_compile
fn main() {
let x = 5;
println!("The value of x is: {}", x);
@@ -40,7 +40,7 @@ error[E0384]: cannot assign twice to immutable variable `x`
在尝试改变预设为不可变的值时,产生编译时错误是很重要的,因为这种情况可能导致 bug。如果一部分代码假设一个值永远也不会改变,而另一部分代码改变了这个值,第一部分代码就有可能以不可预料的方式运行。不得不承认这种 bug 的起因难以跟踪,尤其是第二部分代码只是 **有时** 会改变值。
-Rust 编译器保证,如果声明一个值不会变,它就真的不会变。这意味着当阅读和编写代码时,不需要追踪一个值如何以及哪里可能会被改变,从而使得代码易于推导。
+Rust 编译器保证,如果声明一个值不会变,它就真的不会变。这意味着当阅读和编写代码时,不需要追踪一个值如何和在哪可能会被改变,从而使得代码易于推导。
不过可变性也是非常有用的。变量只是默认不可变;正如在第二章所做的那样,你可以在变量名之前加 `mut` 来使其可变。除了允许改变值之外,`mut` 向读者表明了其他代码将会改变这个变量值的意图。
@@ -84,7 +84,7 @@ The value of x is: 6
最后一个区别是,常量只能被设置为常量表达式,而不能是函数调用的结果,或任何其他只能在运行时计算出的值。
-这是一个声明常量的例子,它的名称是 `MAX_POINTS`,值是 100,000。(Rust 常量的命名规范是使用下划线分隔的大写字母):
+这是一个声明常量的例子,它的名称是 `MAX_POINTS`,值是 100,000。(Rust 常量的命名规范是使用下划线分隔的大写字母单词,并且可以在数字字面值中插入下划线来提升可读性):
```rust
const MAX_POINTS: u32 = 100_000;
@@ -133,7 +133,7 @@ let spaces = spaces.len();
这里允许第一个 `spaces` 变量是字符串类型,而第二个 `spaces` 变量,它是一个恰巧与第一个变量同名的崭新变量,是数字类型。隐藏使我们不必使用不同的名字,如 `spaces_str` 和 `spaces_num`;相反,我们可以复用 `spaces` 这个更简单的名字。然而,如果尝试使用 `mut`,将会得到一个编译时错误,如下所示:
-```rust,ignore
+```rust,ignore,does_not_compile
let mut spaces = " ";
spaces = spaces.len();
```
diff --git a/src/ch03-02-data-types.md b/src/ch03-02-data-types.md
index 7c6900a..4c7fdc3 100644
--- a/src/ch03-02-data-types.md
+++ b/src/ch03-02-data-types.md
@@ -1,10 +1,10 @@
## 数据类型
-> [ch03-02-data-types.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch03-02-data-types.md)
+> [ch03-02-data-types.md](https://github.com/rust-lang/book/blob/master/src/ch03-02-data-types.md)
>
-> commit f949ff883628db8ed2f2f5f19e146ebf19ed6a6f
+> commit a86c1d315789b3ca13b20d50ad5005c62bdd9e37
-在 Rust 中,每一个值都属于某一个 **数据类型**(*data type*),这告诉 Rust 它被指定为何种数据,以便明确数据处理方式。我们将看到两类数据类型:标量(scalar)和复合(compound)。
+在 Rust 中,每一个值都属于某一个 **数据类型**(*data type*),这告诉 Rust 它被指定为何种数据,以便明确数据处理方式。我们将看到两类数据类型子集:标量(scalar)和复合(compound)。
记住,Rust 是 **静态类型**(*statically typed*)语言,也就是说在编译时就必须知道所有变量的类型。根据值及其使用方式,编译器通常可以推断出我们想要用的类型。当多种类型均有可能时,比如第二章的 “比较猜测的数字和秘密数字” 使用 `parse` 将 `String` 转换为数字时,必须增加类型注解,像这样:
@@ -65,6 +65,12 @@ error[E0282]: type annotations needed
那么该使用哪种类型的数字呢?如果拿不定主意,Rust 的默认类型通常就很好,数字类型默认是 `i32`:它通常是最快的,甚至在 64 位系统上也是。`isize` 或 `usize` 主要作为某些集合的索引。
+##### 整型溢出
+
+比方说有一个 `u8` ,它可以存放从零到 `255` 的值。那么当你将其修改为 `256` 时会发生什么呢?这被称为 “整型溢出”(“integer overflow” ),关于这一行为 Rust 有一些有趣的规则。当在 debug 模式编译时,Rust 检查这类问题并使程序 *panic*,这个术语被 Rust 用来表明程序因错误而退出。第九章会详细介绍 panic。
+
+在 release 构建中,Rust 不检测溢出,相反会进行一种被称为 “two’s complement wrapping” 的操作。简而言之,`256` 变成 `0`,`257` 变成 `1`,依此类推。依赖溢出被认为是一种错误,即便可能出现这种行为。如果你确实需要这种行为,标准库中有一个类型显式提供此功能,`Wrapping`。
+
#### 浮点型
Rust 也有两个原生的 **浮点数**(*floating-point numbers*)类型,它们是带小数点的数字。Rust 的浮点数类型是 `f32` 和 `f64`,分别占 32 位和 64 位。默认类型是 `f64`,因为在现代 CPU 中,它与 `f32` 速度几乎一样,不过精度更高。
@@ -217,6 +223,14 @@ let months = ["January", "February", "March", "April", "May", "June", "July",
"August", "September", "October", "November", "December"];
```
+数组的类型比较有趣;它看起来像 `[type; number]`。例如:
+
+```rust
+let a: [i32; 5] = [1, 2, 3, 4, 5];
+```
+
+首先是方括号;这看起来像创建数组的语法。其中有两部分由分号分割的信息。第一部分是数组中每个元素的类型。因为所有元素都是相同类型的,所以只需列出一次。分号之后,是一个表示数组长度的数字。因为数组是固定长度的,该数字也一直保持不变,即便数组的元素被修改,它也不会增长或缩小。
+
##### 访问数组元素
数组是一整块分配在栈上的内存。可以使用索引来访问数组的元素,像这样:
diff --git a/src/ch03-03-how-functions-work.md b/src/ch03-03-how-functions-work.md
index 6035378..987e945 100644
--- a/src/ch03-03-how-functions-work.md
+++ b/src/ch03-03-how-functions-work.md
@@ -1,8 +1,8 @@
## 函数
-> [ch03-03-how-functions-work.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch03-03-how-functions-work.md)
+> [ch03-03-how-functions-work.md](https://github.com/rust-lang/book/blob/master/src/ch03-03-how-functions-work.md)
>
-> commit f949ff883628db8ed2f2f5f19e146ebf19ed6a6f
+> commit a86c1d315789b3ca13b20d50ad5005c62bdd9e37
函数遍布于 Rust 代码中。你已经见过语言中最重要的函数之一:`main` 函数,它是很多程序的入口点。你也见过 `fn` 关键字,它用来声明新函数。
@@ -41,7 +41,7 @@ Another function.
### 函数参数
-函数也可以被定义为拥有 **参数**(*parameters*),参数是特殊变量,是函数签名的一部分。当函数拥有参数(形参)时,可以为这些参数提供具体的值(实参)。技术上讲,这些具体值被称为参数(*arguments*),但是在闲谈时,人们倾向于互换着使用 *parameter* 和 *argument* 来表示函数定义中的变量或调用函数时传入的具体值。
+函数也可以被定义为拥有 **参数**(*parameters*),参数是特殊变量,是函数签名的一部分。当函数拥有参数(形参)时,可以为这些参数提供具体的值(实参)。技术上讲,这些具体值被称为参数(*arguments*),但是在日常交流中,人们倾向于不区分使用 *parameter* 和 *argument* 来表示函数定义中的变量或调用函数时传入的具体值。
下面被重写的 `another_function` 版本展示了 Rust 中参数是什么样的:
@@ -125,7 +125,7 @@ fn main() {
文件名: src/main.rs
-```rust,ignore
+```rust,ignore,does_not_compile
fn main() {
let x = (let y = 6);
}
@@ -231,7 +231,7 @@ fn plus_one(x: i32) -> i32 {
文件名: src/main.rs
-```rust,ignore
+```rust,ignore,does_not_compile
fn main() {
let x = plus_one(5);
diff --git a/src/ch03-04-comments.md b/src/ch03-04-comments.md
index 2aecde8..84bcd55 100644
--- a/src/ch03-04-comments.md
+++ b/src/ch03-04-comments.md
@@ -1,8 +1,8 @@
## 注释
-> [ch03-04-comments.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch03-04-comments.md)
+> [ch03-04-comments.md](https://github.com/rust-lang/book/blob/master/src/ch03-04-comments.md)
>
-> commit f949ff883628db8ed2f2f5f19e146ebf19ed6a6f
+> commit a86c1d315789b3ca13b20d50ad5005c62bdd9e37
所有程序员都力求使其代码易于理解,不过有时还需要提供额外的解释。在这种情况下,程序员在源码中留下记录,或者 **注释**(*comments*),编译器会忽略它们,不过阅读代码的人可能觉得有用。
diff --git a/src/ch03-05-control-flow.md b/src/ch03-05-control-flow.md
index c0295d7..2494982 100644
--- a/src/ch03-05-control-flow.md
+++ b/src/ch03-05-control-flow.md
@@ -1,8 +1,8 @@
## 控制流
-> [ch03-05-control-flow.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch03-05-control-flow.md)
+> [ch03-05-control-flow.md](https://github.com/rust-lang/book/blob/master/src/ch03-05-control-flow.md)
>
-> commit f949ff883628db8ed2f2f5f19e146ebf19ed6a6f
+> commit a86c1d315789b3ca13b20d50ad5005c62bdd9e37
根据条件是否为真来决定是否执行某些代码,以及根据条件是否为真来重复运行一段代码是大部分编程语言的基本组成部分。Rust 代码中最常见的用来控制执行流的结构是 `if` 表达式和循环。
@@ -62,7 +62,7 @@ condition was false
文件名: src/main.rs
-```rust,ignore
+```rust,ignore,does_not_compile
fn main() {
let number = 3;
@@ -172,7 +172,7 @@ The value of number is: 5
文件名: src/main.rs
-```rust,ignore
+```rust,ignore,does_not_compile
fn main() {
let condition = true;
@@ -246,6 +246,26 @@ again!
幸运的是,Rust 提供了另一种更可靠的退出循环的方式。可以使用 `break` 关键字来告诉程序何时停止循环。回忆一下在第二章猜猜看游戏的 “猜测正确后退出” 部分使用过它来在用户猜对数字赢得游戏后退出程序。
+#### 从循环返回
+
+`loop` 的一个用例是重试可能会失败的操作,比如检查线程是否完成了任务。然而你可能会需要将操作的结果传递给其它的代码。如果将返回值加入你用来停止循环的 `break` 表达式,它会被停止的循环返回:
+
+```rust
+fn main() {
+ let mut counter = 0;
+
+ let result = loop {
+ counter += 1;
+
+ if counter == 10 {
+ break counter * 2;
+ }
+ };
+
+ assert_eq!(result, 20);
+}
+```
+
#### `while` 条件循环
在程序中计算循环的条件也很常见。当条件为真,执行循环。当条件不再为真,调用 `break` 停止循环。这个循环类型可以通过组合 `loop`、`if`、`else` 和 `break` 来实现;如果你喜欢的话,现在就可以在程序中试试。
@@ -270,7 +290,7 @@ fn main() {
示例 3-3: 当条件为真时,使用 `while` 循环运行代码
-这种结构消除了使用 `loop`、`if`、`else` 和 `break` 时必须有的很多嵌套,代码更加清晰。当条件为真就执行,否则退出循环。
+这种结构消除了很多使用 `loop`、`if`、`else` 和 `break` 时所必须的嵌套,这样更加清晰。当条件为真就执行,否则退出循环。
#### 使用 `for` 遍历集合
diff --git a/src/ch04-00-understanding-ownership.md b/src/ch04-00-understanding-ownership.md
index d0045c9..327c43c 100644
--- a/src/ch04-00-understanding-ownership.md
+++ b/src/ch04-00-understanding-ownership.md
@@ -1,7 +1,7 @@
# 认识所有权
-> [ch04-00-understanding-ownership.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch04-00-understanding-ownership.md)
+> [ch04-00-understanding-ownership.md](https://github.com/rust-lang/book/blob/master/src/ch04-00-understanding-ownership.md)
>
-> commit aff4f619c4d6dc138b57b74c3a898ba9bce06649
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
所有权(系统)是 Rust 最独特的功能,其令 Rust 无需垃圾回收(garbage collector)即可保障内存安全。因此,理解 Rust 中所有权如何工作是十分重要的。本章,我们将讲到所有权以及相关功能:借用、slice 以及 Rust 如何在内存中布局数据。
\ No newline at end of file
diff --git a/src/ch04-01-what-is-ownership.md b/src/ch04-01-what-is-ownership.md
index ddf5a9c..132cccc 100644
--- a/src/ch04-01-what-is-ownership.md
+++ b/src/ch04-01-what-is-ownership.md
@@ -1,8 +1,8 @@
## 什么是所有权
-> [ch04-01-what-is-ownership.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch04-01-what-is-ownership.md)
+> [ch04-01-what-is-ownership.md](https://github.com/rust-lang/book/blob/master/src/ch04-01-what-is-ownership.md)
>
-> commit f949ff883628db8ed2f2f5f19e146ebf19ed6a6f
+> commit a86c1d315789b3ca13b20d50ad5005c62bdd9e37
Rust 的核心功能(之一)是 **所有权**(*ownership*)。虽然该功能很容易解释,但它对语言的其他部分有着深刻的影响。
@@ -165,11 +165,11 @@ let s2 = s1;
图 4-3:另一个 `s2 = s1` 时可能的内存表现,如果 Rust 同时也拷贝了堆上的数据的话
-之前,我们提到过当变量离开作用域后,Rust 自动调用 `drop` 函数并清理变量的堆内存。不过图 4-2 展示了两个数据指针指向了同一位置。这就有了一个问题:当 `s2` 和 `s1` 离开作用域,他们都会尝试释放相同的内存。这是一个叫做 **二次释放**(*double free*)的错误,也是之前提到过的内存安全性 bug 之一。两次释放(相同)内存会导致内存污染,它可能会导致潜在的安全漏洞。
+之前我们提到过当变量离开作用域后,Rust 自动调用 `drop` 函数并清理变量的堆内存。不过图 4-2 展示了两个数据指针指向了同一位置。这就有了一个问题:当 `s2` 和 `s1` 离开作用域,他们都会尝试释放相同的内存。这是一个叫做 **二次释放**(*double free*)的错误,也是之前提到过的内存安全性 bug 之一。两次释放(相同)内存会导致内存污染,它可能会导致潜在的安全漏洞。
-为了确保内存安全,这种场景下 Rust 的处理有另一个细节值得注意。与其尝试拷贝被分配的内存,Rust 则认为 `s1` 不再有效,因此 Rust 不需要在 `s1` 离开作用域后清理任何东西。看看在 `s2` 被创建之后尝试使用 `s1` 会发生什么;它不会工作:
+为了确保内存安全,这种场景下 Rust 的处理有另一个细节值得注意。与其尝试拷贝被分配的内存,Rust 则认为 `s1` 不再有效,因此 Rust 不需要在 `s1` 离开作用域后清理任何东西。看看在 `s2` 被创建之后尝试使用 `s1` 会发生什么;这段代码不能运行:
-```rust,ignore
+```rust,ignore,does_not_compile
let s1 = String::from("hello");
let s2 = s1;
@@ -254,7 +254,7 @@ Rust 有一个叫做 `Copy` trait 的特殊注解,可以用在类似整型这
fn main() {
let s = String::from("hello"); // s 进入作用域
- takes_ownership(s); // s 的值移动到函数里...
+ takes_ownership(s); // s 的值移动到函数里 ...
// ... 所以到这里不再有效
let x = 5; // x 进入作用域
diff --git a/src/ch04-02-references-and-borrowing.md b/src/ch04-02-references-and-borrowing.md
index 446512b..d0be48c 100644
--- a/src/ch04-02-references-and-borrowing.md
+++ b/src/ch04-02-references-and-borrowing.md
@@ -1,8 +1,8 @@
## 引用与借用
-> [ch04-02-references-and-borrowing.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch04-02-references-and-borrowing.md)
+> [ch04-02-references-and-borrowing.md](https://github.com/rust-lang/book/blob/master/src/ch04-02-references-and-borrowing.md)
>
-> commit f949ff883628db8ed2f2f5f19e146ebf19ed6a6f
+> commit a86c1d315789b3ca13b20d50ad5005c62bdd9e37
示例 4-5 中的元组代码有这样一个问题:我们必须将 `String` 返回给调用函数,以便在调用 `calculate_length` 后仍能使用 `String`,因为 `String` 被移动到了 `calculate_length` 内。
@@ -64,7 +64,7 @@ fn calculate_length(s: &String) -> usize { // s 是对 String 的引用
文件名: src/main.rs
-```rust,ignore
+```rust,ignore,does_not_compile
fn main() {
let s = String::from("hello");
@@ -114,25 +114,30 @@ fn change(some_string: &mut String) {
不过可变引用有一个很大的限制:在特定作用域中的特定数据有且只有一个可变引用。这些代码会失败:
-```rust,ignore
+文件名: src/main.rs
+
+```rust,ignore,does_not_compile
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s;
+
+println!("{}, {}", r1, r2);
```
+
错误如下:
```text
error[E0499]: cannot borrow `s` as mutable more than once at a time
- --> borrow_twice.rs:5:19
+ --> src/main.rs:5:10
|
-4 | let r1 = &mut s;
- | - first mutable borrow occurs here
-5 | let r2 = &mut s;
- | ^ second mutable borrow occurs here
-6 | }
- | - first borrow ends here
+4 | let r1 = &mut s;
+ | ------ first mutable borrow occurs here
+5 | let r2 = &mut s;
+ | ^^^^^^ second mutable borrow occurs here
+6 | println!("{}, {}", r1, r2);
+ | -- borrow later used here
```
这个限制允许可变性,不过是以一种受限制的方式允许。新 Rustacean 们经常与此作斗争,因为大部分语言中变量任何时候都是可变的。
@@ -160,28 +165,30 @@ let r2 = &mut s;
类似的规则也存在于同时使用可变与不可变引用中。这些代码会导致一个错误:
-```rust,ignore
+```rust,ignore,does_not_compile
let mut s = String::from("hello");
let r1 = &s; // no problem
let r2 = &s; // no problem
let r3 = &mut s; // BIG PROBLEM
+
+println!("{}, {}, and {}", r1, r2, r3);
```
错误如下:
```text
-error[E0502]: cannot borrow `s` as mutable because it is also borrowed as
-immutable
- --> borrow_thrice.rs:6:19
+error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
+ --> src/main.rs:6:10
|
-4 | let r1 = &s; // no problem
- | - immutable borrow occurs here
-5 | let r2 = &s; // no problem
-6 | let r3 = &mut s; // BIG PROBLEM
- | ^ mutable borrow occurs here
-7 | }
- | - immutable borrow ends here
+4 | let r1 = &s; // no problem
+ | -- immutable borrow occurs here
+5 | let r2 = &s; // no problem
+6 | let r3 = &mut s; // BIG PROBLEM
+ | ^^^^^^ mutable borrow occurs here
+7 |
+8 | println!("{}, {}, and {}", r1, r2, r3);
+ | -- borrow later used here
```
哇哦!我们 **也** 不能在拥有不可变引用的同时拥有可变引用。不可变引用的用户可不希望在他们的眼皮底下值就被意外的改变了!然而,多个不可变引用是可以的,因为没有哪个只能读取数据的人有能力影响其他人读取到的数据。
@@ -196,7 +203,7 @@ immutable
文件名: src/main.rs
-```rust,ignore
+```rust,ignore,does_not_compile
fn main() {
let reference_to_nothing = dangle();
}
@@ -231,15 +238,13 @@ for it to be borrowed from.
让我们仔细看看我们的 `dangle` 代码的每一步到底发生了什么:
-Filename: src/main.rs
-
```rust,ignore
-fn dangle() -> &String { // dangle 返回一个 String 引用
+fn dangle() -> &String { // dangle 返回一个字符串的引用
- let s = String::from("hello"); // s 是新创建的 String
+ let s = String::from("hello"); // s 是一个新字符串
- &s // 我们返回 String 的引用,s
-} // 这里,s 移出了作用域,并被丢弃。它的内存被释放
+ &s // 返回字符串 s 的引用
+} // 这里 s 离开作用域并被丢弃。其内存被释放。
// 危险!
```
diff --git a/src/ch04-03-slices.md b/src/ch04-03-slices.md
index c4f5a58..ee936f8 100644
--- a/src/ch04-03-slices.md
+++ b/src/ch04-03-slices.md
@@ -1,8 +1,8 @@
## Slice 类型
-> [ch04-03-slices.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch04-03-slices.md)
+> [ch04-03-slices.md](https://github.com/rust-lang/book/blob/master/src/ch04-03-slices.md)
>
-> commit 729f1118332ddcf01138d284eac5db0e85f8c8ed
+> commit a86c1d315789b3ca13b20d50ad5005c62bdd9e37
另一个没有所有权的数据类型是 *slice*。slice 允许你引用集合中一段连续的元素序列,而不用引用整个集合。
@@ -115,9 +115,18 @@ let hello = &s[0..5];
let world = &s[6..11];
```
-这类似于引用整个 `String` 不过带有额外的 `[0..5]` 部分。不是对整个 `String` 的引用,而是对部分 `String` 的引用。`start..end` 语法代表一个以 `start` 开头并一直持续到但不包含 `end` 的 range。
+这类似于引用整个 `String` 不过带有额外的 `[0..5]` 部分。它不是对整个 `String` 的引用,而是对部分 `String` 的引用。`start..end` 语法代表一个以 `start` 开头并一直持续到但不包含 `end` 的 range。如果需要包含 `end`,可以使用 `..=` 而不是 `..`:
-使用一个由中括号中的 `[starting_index..ending_index]` 指定的 range 创建一个 slice,其中 `starting_index` 是 slice 的第一个位置,`ending_index` 则是 slice 最后一个位置的后一个值。在其内部,slice 的数据结构存储了 slice 的开始位置和长度,长度对应于 `ending_index` 减去 `starting_index` 的值。所以对于 `let world = &s[6..11];` 的情况,`world` 将是一个包含指向 `s` 第 7 个字节的指针和长度值 5 的 slice。
+```rust
+let s = String::from("hello world");
+
+let hello = &s[0..=4];
+let world = &s[6..=10];
+```
+
+`=` 意味着包含最后的数字,这有助于你记住 `..` 与 `..=` 的区别
+
+可以使用一个由中括号中的 `[starting_index..ending_index]` 指定的 range 创建一个 slice,其中 `starting_index` 是 slice 的第一个位置,`ending_index` 则是 slice 最后一个位置的后一个值。在其内部,slice 的数据结构存储了 slice 的开始位置和长度,长度对应于 `ending_index` 减去 `starting_index` 的值。所以对于 `let world = &s[6..11];` 的情况,`world` 将是一个包含指向 `s` 第 7 个字节的指针和长度值 5 的 slice。
图 4-6 展示了一个图例。
@@ -190,13 +199,15 @@ fn second_word(s: &String) -> &str {
文件名: src/main.rs
-```rust,ignore
+```rust,ignore,does_not_compile
fn main() {
let mut s = String::from("hello world");
let word = first_word(&s);
s.clear(); // error!
+
+ println!("the first word is: {}", word);
}
```
@@ -204,15 +215,16 @@ fn main() {
```text
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
- --> src/main.rs:6:5
- |
-4 | let word = first_word(&s);
- | - immutable borrow occurs here
-5 |
-6 | s.clear(); // error!
- | ^ mutable borrow occurs here
-7 | }
- | - immutable borrow ends here
+ --> src/main.rs:10:5
+ |
+8 | let word = first_word(&s);
+ | -- immutable borrow occurs here
+9 |
+10 | s.clear(); // error!
+ | ^^^^^^^^^ mutable borrow occurs here
+11 |
+12 | println!("the first word is: {}", word);
+ | ---- borrow later used here
```
回忆一下借用规则,当拥有某值的不可变引用时,就不能再获取一个可变引用。因为 `clear` 需要清空 `String`,它尝试获取一个可变引用,它失败了。Rust 不仅使得我们的 API 简单易用,也在编译时就消除了一整类的错误!
@@ -245,7 +257,7 @@ fn first_word(s: &str) -> &str {
如果有一个字符串 slice,可以直接传递它。如果有一个 `String`,则可以传递整个 `String` 的 slice。定义一个获取字符串 slice 而不是 `String` 引用的函数使得我们的 API 更加通用并且不会丢失任何功能:
-Filename: src/main.rs
+文件名: src/main.rs
```rust
# fn first_word(s: &str) -> &str {
diff --git a/src/ch05-00-structs.md b/src/ch05-00-structs.md
index fb9c764..9b07278 100644
--- a/src/ch05-00-structs.md
+++ b/src/ch05-00-structs.md
@@ -1,7 +1,7 @@
# 使用结构体组织相关联的数据
-> [ch05-00-structs.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch05-00-structs.md)
+> [ch05-00-structs.md](https://github.com/rust-lang/book/blob/master/src/ch05-00-structs.md)
>
-> commit 5e2368866cb445d912f5d2a2c92f4b42bcb542bb
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
*struct*,或者 *structure*,是一个自定义数据类型,允许你命名和包装多个相关的值,从而形成一个有意义的组合。如果你熟悉一门面向对象语言,*struct* 就像对象中的数据属性。在本章中,我们会对比元组与结构体的异同,演示结构体的用法,并讨论如何在结构体上定义方法和关联函数来指定与结构体数据相关的行为。你在程序中基于结构体和枚举(*enum*)(在第六章介绍)创建新类型,以充分利用 Rust 的编译时类型检查。
diff --git a/src/ch05-01-defining-structs.md b/src/ch05-01-defining-structs.md
index 13805b6..56bf7e7 100644
--- a/src/ch05-01-defining-structs.md
+++ b/src/ch05-01-defining-structs.md
@@ -1,8 +1,8 @@
## 定义并实例化结构体
-> [ch05-01-defining-structs.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch05-01-defining-structs.md)
+> [ch05-01-defining-structs.md](https://github.com/rust-lang/book/blob/master/src/ch05-01-defining-structs.md)
>
-> commit c560db1e0145d5a64b9415c9cfe463c7dac31ab8
+> commit a86c1d315789b3ca13b20d50ad5005c62bdd9e37
结构体和我们在第三章讨论过的元组类似。和元组一样,结构体的每一部分可以是不同类型。但不同于元组,结构体需要命名各部分数据以便能清楚的表明其值的意义。由于有了这些名字,结构体比元组更灵活:不需要依赖顺序来指定或访问实例中的值。
@@ -192,7 +192,7 @@ let origin = Point(0, 0, 0);
我们也可以定义一个没有任何字段的结构体!它们被称为 **类单元结构体**(*unit-like structs*)因为它们类似于 `()`,即 unit 类型。类单元结构体常常在你想要在某个类型上实现 trait 但不需要在类型中存储数据的时候发挥作用。我们将在第十章介绍 trait。
-> ## 结构体数据的所有权
+> ### 结构体数据的所有权
>
> 在示例 5-1 中的 `User` 结构体的定义中,我们使用了自身拥有所有权的 `String` 类型而不是 `&str` 字符串 slice 类型。这是一个有意而为之的选择,因为我们想要这个结构体拥有它所有的数据,为此只要整个结构体是有效的话其数据也是有效的。
>
@@ -200,7 +200,7 @@ let origin = Point(0, 0, 0);
>
> 文件名: src/main.rs
>
-> ```rust,ignore
+> ```rust,ignore,does_not_compile
> struct User {
> username: &str,
> email: &str,
diff --git a/src/ch05-02-example-structs.md b/src/ch05-02-example-structs.md
index 956845a..b010581 100644
--- a/src/ch05-02-example-structs.md
+++ b/src/ch05-02-example-structs.md
@@ -1,8 +1,8 @@
## 一个使用结构体的示例程序
-> [ch05-02-example-structs.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch05-02-example-structs.md)
+> [ch05-02-example-structs.md](https://github.com/rust-lang/book/blob/master/src/ch05-02-example-structs.md)
>
-> commit c560db1e0145d5a64b9415c9cfe463c7dac31ab8
+> commit a86c1d315789b3ca13b20d50ad5005c62bdd9e37
为了理解何时会需要使用结构体,让我们编写一个计算长方形面积的程序。我们会从单独的变量开始,接着重构程序直到使用结构体替代他们为止。
@@ -111,7 +111,7 @@ fn area(rectangle: &Rectangle) -> u32 {
文件名: src/main.rs
-```rust,ignore
+```rust,ignore,does_not_compile
struct Rectangle {
width: u32,
height: u32,
@@ -149,7 +149,7 @@ error[E0277]: the trait bound `Rectangle: std::fmt::Display` is not satisfied
error[E0277]: the trait bound `Rectangle: std::fmt::Debug` is not satisfied
```
-不过编译器又一次给出了一个有帮助的信息!
+不过编译器又一次给出了一个有帮助的信息:
```text
`Rectangle` cannot be formatted using `:?`; if it is defined in your
diff --git a/src/ch05-03-method-syntax.md b/src/ch05-03-method-syntax.md
index 2728c95..12ecdc7 100644
--- a/src/ch05-03-method-syntax.md
+++ b/src/ch05-03-method-syntax.md
@@ -1,8 +1,8 @@
## 方法语法
-> [ch05-03-method-syntax.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch05-03-method-syntax.md)
+> [ch05-03-method-syntax.md](https://github.com/rust-lang/book/blob/master/src/ch05-03-method-syntax.md)
>
-> commit c560db1e0145d5a64b9415c9cfe463c7dac31ab8
+> commit a86c1d315789b3ca13b20d50ad5005c62bdd9e37
**方法** 与函数类似:它们使用 `fn` 关键字和名称声明,可以拥有参数和返回值,同时包含在某处调用该方法时会执行的代码。不过方法与函数是不同的,因为它们在结构体的上下文中被定义(或者是枚举或 trait 对象的上下文,将分别在第六章和第十七章讲解),并且它们第一个参数总是 `self`,它代表调用该方法的结构体实例。
diff --git a/src/ch06-00-enums.md b/src/ch06-00-enums.md
index c6fb8c5..98e58f0 100644
--- a/src/ch06-00-enums.md
+++ b/src/ch06-00-enums.md
@@ -1,8 +1,8 @@
# 枚举和模式匹配
-> [ch06-00-enums.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch06-00-enums.md)
+> [ch06-00-enums.md](https://github.com/rust-lang/book/blob/master/src/ch06-00-enums.md)
>
-> commit e3be64b3c034de029ae9e4f04e6d2742d799d2b1
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
本章介绍 **枚举**(*enumerations*),也被称作 *enums*。枚举允许你通过列举可能的值来定义一个类型。首先,我们会定义并使用一个枚举来展示它是如何连同数据一起编码信息的。接下来,我们会探索一个特别有用的枚举,叫做 `Option`,它代表一个值要么是某个值要么什么都不是。然后会讲到在 `match` 表达式中用模式匹配,针对不同的枚举值编写相应要执行的代码。最后会介绍 `if let`,另一个简洁方便处理代码中枚举的结构。
diff --git a/src/ch06-01-defining-an-enum.md b/src/ch06-01-defining-an-enum.md
index 656aab3..ebb9323 100644
--- a/src/ch06-01-defining-an-enum.md
+++ b/src/ch06-01-defining-an-enum.md
@@ -1,8 +1,8 @@
# 定义枚举
-> [ch06-01-defining-an-enum.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch06-01-defining-an-enum.md)
+> [ch06-01-defining-an-enum.md](https://github.com/rust-lang/book/blob/master/src/ch06-01-defining-an-enum.md)
>
-> commit 923201d5117c45bf78ce433422b50e4de9bd9b11
+> commit a86c1d315789b3ca13b20d50ad5005c62bdd9e37
让我们看看一个需要诉诸于代码的场景,来考虑为何此时使用枚举更为合适且实用。假设我们要处理 IP 地址。目前被广泛使用的两个主要 IP 标准:IPv4(version four)和 IPv6(version six)。这是我们的程序可能会遇到的所有可能的 IP 地址类型:所以可以 **枚举** 出所有可能的值,这也正是此枚举名字的由来。
@@ -157,7 +157,7 @@ enum Message {
* `Write` 包含单独一个 `String`。
* `ChangeColor` 包含三个 `i32`。
-定义一个如示例 6-2 中所示那样的有关联值的枚举的方式和定义多个不同类型的结构体的方式很相像——除了枚举不使用 `struct` 关键字以及其所有成员都被组合在一起位于 `Message` 类型下。如下这些结构体可以包含与之前枚举成员中相同的数据:
+定义一个如示例 6-2 中所示那样的有关联值的枚举的方式和定义多个不同类型的结构体的方式很相像,除了枚举不使用 `struct` 关键字以及其所有成员都被组合在一起位于 `Message` 类型下。如下这些结构体可以包含与之前枚举成员中相同的数据:
```rust
struct QuitMessage; // 类单元结构体
@@ -169,7 +169,7 @@ struct WriteMessage(String); // 元组结构体
struct ChangeColorMessage(i32, i32, i32); // 元组结构体
```
-不过,如果我们使用不同的结构体,由于它们都有不同的类型,我们将不能像使用示例 6-2 中定义的 `Message` 枚举那样,轻易的定义一个能够处理这些不同类型的结构体的函数。因为使用枚举的情况下,“它们”是一个类型。
+不过,如果我们使用不同的结构体,由于它们都有不同的类型,我们将不能像使用示例 6-2 中定义的 `Message` 枚举那样,轻易的定义一个能够处理这些不同类型的结构体的函数,因为枚举是单独一个类型。
结构体和枚举还有另一个相似点:就像可以使用 `impl` 来为结构体定义方法那样,也可以在枚举上定义方法。这是一个定义于我们 `Message` 枚举上的叫做 `call` 的方法:
diff --git a/src/ch06-02-match.md b/src/ch06-02-match.md
index a950e5c..1c80c06 100644
--- a/src/ch06-02-match.md
+++ b/src/ch06-02-match.md
@@ -1,8 +1,8 @@
## `match` 控制流运算符
-> [ch06-02-match.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch06-02-match.md)
+> [ch06-02-match.md](https://github.com/rust-lang/book/blob/master/src/ch06-02-match.md)
>
-> commit 18fd30d70f4d6ee67e0a808710bf7a3135ef7ed6
+> commit a86c1d315789b3ca13b20d50ad5005c62bdd9e37
Rust 有一个叫做 `match` 的极为强大的控制流运算符,它允许我们将一个值与一系列的模式相比较并根据相匹配的模式执行相应代码。模式可由字面值、变量、通配符和许多其他内容构成;第十八章会涉及到所有不同种类的模式以及它们的作用。`match` 的力量来源于模式的表现力以及编译器检查,它确保了所有可能的情况都得到处理。
@@ -30,11 +30,11 @@ fn value_in_cents(coin: Coin) -> u32 {
示例 6-3:一个枚举和一个以枚举成员作为模式的 `match` 表达式
-拆开 `value_in_cents` 函数中的 `match` 来看。首先,我们列出 `match` 关键字后跟一个表达式,在这个例子中是 `coin` 的值。这看起来非常像 `if` 使用的表达式,不过这里有一个非常大的区别:对于 `if`,表达式必须返回一个布尔值。而这里它可以是任何类型的。例子中的 `coin` 的类型是示例 6-3 中定义的 `Coin` 枚举。
+拆开 `value_in_cents` 函数中的 `match` 来看。首先,我们列出 `match` 关键字后跟一个表达式,在这个例子中是 `coin` 的值。这看起来非常像 `if` 使用的表达式,不过这里有一个非常大的区别:对于 `if`,表达式必须返回一个布尔值,而这里它可以是任何类型的。例子中的 `coin` 的类型是示例 6-3 中定义的 `Coin` 枚举。
接下来是 `match` 的分支。一个分支有两个部分:一个模式和一些代码。第一个分支的模式是值 `Coin::Penny` 而之后的 `=>` 运算符将模式和将要运行的代码分开。这里的代码就仅仅是值 `1`。每一个分支之间使用逗号分隔。
-当 `match` 表达式执行时,它将结果值按顺序与每一个分支的模式相比较,如果模式匹配了这个值,这个模式相关联的代码将被执行。如果模式并不匹配这个值,将继续执行下一个分支,非常类似一个硬币分类器。可以拥有任意多的分支:示例 6-3 中的 `match` 有四个分支。
+当 `match` 表达式执行时,它将结果值按顺序与每一个分支的模式相比较。如果模式匹配了这个值,这个模式相关联的代码将被执行。如果模式并不匹配这个值,将继续执行下一个分支,非常类似一个硬币分类器。可以拥有任意多的分支:示例 6-3 中的 `match` 有四个分支。
每个分支相关联的代码是一个表达式,而表达式的结果值将作为整个 `match` 表达式的返回值。
@@ -68,11 +68,11 @@ fn value_in_cents(coin: Coin) -> u32 {
作为一个例子,让我们修改枚举的一个成员来存放数据。1999 年到 2008 年间,美帝在 25 美分的硬币的一侧为 50 个州的每一个都印刷了不同的设计。其他的硬币都没有这种区分州的设计,所以只有这些 25 美分硬币有特殊的价值。可以将这些信息加入我们的 `enum`,通过改变 `Quarter` 成员来包含一个 `State` 值,示例 6-4 中完成了这些修改:
```rust
-#[derive(Debug)] // So we can inspect the state in a minute
+#[derive(Debug)] // 这样可以可以立刻看到州的名称
enum UsState {
Alabama,
Alaska,
- // ... etc
+ // --snip--
}
enum Coin {
@@ -120,7 +120,7 @@ fn value_in_cents(coin: Coin) -> u32 {
### 匹配 `Option`
-在之前的部分在使用 `Option` 时我们想要从 `Some` 中取出其内部的 `T` 值;也可以像处理 `Coin` 枚举那样使用 `match` 处理 `Option`!与其直接比较硬币,我们将比较 `Option` 的成员,不过 `match` 表达式的工作方式保持不变。
+我们在之前的部分中使用 `Option` 时,是为了从 `Some` 中取出其内部的 `T` 值;我们还可以像处理 `Coin` 枚举那样使用 `match` 处理 `Option`!与其直接比较硬币,我们将比较 `Option` 的成员,不过 `match` 表达式的工作方式保持不变。
比如我们想要编写一个函数,它获取一个 `Option` 并且如果其中有一个值,将其加一。如果其中没有值,函数应该返回 `None` 值并不尝试执行任何操作。
@@ -143,7 +143,7 @@ let none = plus_one(None);
#### 匹配 `Some(T)`
-让我们更仔细的检查 `plus_one` 的第一行操作。当调用 `plus_one(five)` 时,`plus_one` 函数体中的 `x` 将会是值 `Some(5)`。接着将其与每个分支比较。
+让我们更仔细地检查 `plus_one` 的第一行操作。当调用 `plus_one(five)` 时,`plus_one` 函数体中的 `x` 将会是值 `Some(5)`。接着将其与每个分支比较。
```rust,ignore
None => None,
@@ -157,8 +157,6 @@ Some(i) => Some(i + 1),
`Some(5)` 与 `Some(i)` 匹配吗?当然匹配!它们是相同的成员。`i` 绑定了 `Some` 中包含的值,所以 `i` 的值是 `5`。接着匹配分支的代码被执行,所以我们将 `i` 的值加一并返回一个含有值 `6` 的新 `Some`。
-#### 匹配 `None`
-
接着考虑下示例 6-5 中 `plus_one` 的第二个调用,这里 `x` 是 `None`。我们进入 `match` 并与第一个分支相比较。
```rust,ignore
@@ -171,9 +169,9 @@ None => None,
### 匹配是穷尽的
-`match` 还有另一方面需要讨论。考虑一下 `plus_one` 函数的这个版本:
+`match` 还有另一方面需要讨论。考虑一下 `plus_one` 函数的这个版本,它有一个 bug 并不能编译:
-```rust,ignore
+```rust,ignore,does_not_compile
fn plus_one(x: Option) -> Option {
match x {
Some(i) => Some(i + 1),
@@ -191,7 +189,7 @@ error[E0004]: non-exhaustive patterns: `None` not covered
| ^ pattern `None` not covered
```
-Rust 知道我们没有覆盖所有可能的情况甚至知道那些模式被忘记了!Rust 中的匹配是 **穷尽的**(*exhaustive*):必须穷举到最后的可能性来使代码有效。特别的在这个 `Option` 的例子中,Rust 防止我们忘记明确的处理 `None` 的情况,这使我们免于假设拥有一个实际上为空的值,这造成了之前提到过的价值亿万的错误。
+Rust 知道我们没有覆盖所有可能的情况甚至知道哪些模式被忘记了!Rust 中的匹配是 **穷尽的**(*exhaustive*):必须穷举到最后的可能性来使代码有效。特别的在这个 `Option` 的例子中,Rust 防止我们忘记明确的处理 `None` 的情况,这使我们免于假设拥有一个实际上为空的值,这造成了之前提到过的价值亿万的错误。
### `_` 通配符
diff --git a/src/ch06-03-if-let.md b/src/ch06-03-if-let.md
index ee60496..a6e75b7 100644
--- a/src/ch06-03-if-let.md
+++ b/src/ch06-03-if-let.md
@@ -1,10 +1,10 @@
## `if let` 简单控制流
-> [ch06-03-if-let.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch06-03-if-let.md)
+> [ch06-03-if-let.md](https://github.com/rust-lang/book/blob/master/src/ch06-03-if-let.md)
>
-> commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56
+> commit a86c1d315789b3ca13b20d50ad5005c62bdd9e37
-`if let` 语法让我们以一种不那么冗长的方式结合 `if` 和 `let`,来处理只匹配一个模式的值而忽略其他模式的情况。考虑示例 6-6 中的程序,它匹配一个 `Option` 值并只希望当值为三时执行代码:
+`if let` 语法让我们以一种不那么冗长的方式结合 `if` 和 `let`,来处理只匹配一个模式的值而忽略其他模式的情况。考虑示例 6-6 中的程序,它匹配一个 `Option` 值并只希望当值为 3 时执行代码:
```rust
let some_u8_value = Some(0u8);
@@ -27,7 +27,7 @@ if let Some(3) = some_u8_value {
}
```
-`if let` 获取通过 `=` 分隔的一个模式和一个表达式。它的工作方式与 `match` 相同,这里的表达式对应 `match` 而模式则对应第一个分支。
+`if let` 获取通过等号分隔的一个模式和一个表达式。它的工作方式与 `match` 相同,这里的表达式对应 `match` 而模式则对应第一个分支。
使用 `if let` 意味着编写更少代码,更少的缩进和更少的样板代码。然而,这样会失去 `match` 强制要求的穷尽性检查。`match` 和 `if let` 之间的选择依赖特定的环境以及增加简洁度和失去穷尽性检查的权衡取舍。
diff --git a/src/ch07-00-modules.md b/src/ch07-00-modules.md
deleted file mode 100644
index 0ac9d9a..0000000
--- a/src/ch07-00-modules.md
+++ /dev/null
@@ -1,15 +0,0 @@
-# 使用模块组织和复用代码
-
-> [ch07-00-modules.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch07-00-modules.md)
->
-> commit 050ff6f34f107b2c8695807fc16aeb827ffe1fa3
-
-在你刚开始编写 Rust 程序时,代码可能仅仅位于 `main` 函数中。随着代码量的增长,为了复用和更好地组织代码,最终你会将功能移动到其他函数中。通过将代码拆分成更小的块,每一个块就更易于理解。但是如果你有太多的函数该怎么办呢?Rust 有一个模块系统,可以有组织地复用代码。
-
-就跟你将代码行提取到一个函数中一样,也可以将函数(和其他代码,例如结构体和枚举)提取到不同模块中。**模块**(*module*)是一个包含函数或类型定义的命名空间,你可以选择这些定义能(公有)或不能(私有)在其模块外可见。下面是一个模块如何工作的梗概:
-
-* 使用 `mod` 关键字声明新模块。此模块中的代码要么直接位于声明之后的大括号中,要么位于另一个文件。
-* 函数、类型、常量和模块默认都是私有的。可以使用 `pub` 关键字将其变成公有并在其命名空间之外可见。
-* `use` 关键字将模块或模块中的定义引入到作用域中以便于引用它们。
-
-我们会逐一了解这每一部分并学习如何将它们结合在一起。
diff --git a/src/ch07-00-packages-crates-and-modules.md.md b/src/ch07-00-packages-crates-and-modules.md.md
new file mode 100644
index 0000000..d2c2dba
--- /dev/null
+++ b/src/ch07-00-packages-crates-and-modules.md.md
@@ -0,0 +1,16 @@
+# 包、crate 与 模块
+
+> [ch07-00-packages-crates-and-modules.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch07-00-modules.md)
+>
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
+
+编写程序时一个核心的问题是 **作用域**(*scope*):在代码的某处编译器知道哪些变量名?允许调用哪些函数?这些变量引用的又是什么?
+
+Rust 有一系列与作用域相关的功能。这有时被称为 “模块系统”(“the module system”),不过又不仅仅是模块:
+
+* **包**(*Packages*)是 Cargo 的一个功能,它允许你构建、测试核分享 crate。
+* *Crates* 是一个模块的树形结构,它形成了库或二进制项目。
+* **模块**(*Modules*)和 *use* 关键字允许你控制作用域和路径的私有性。
+* **路径**(*path*)是一个命名例如结构体、函数或模块等项的方式
+
+本章将会覆盖所有这些概念。很快我们就能像专家一样将命名引入作用域、定义作用域和将命名导出到作用域!
diff --git a/src/ch07-01-packages-and-crates-for-making-libraries-and-executables.md b/src/ch07-01-packages-and-crates-for-making-libraries-and-executables.md
new file mode 100644
index 0000000..db08964
--- /dev/null
+++ b/src/ch07-01-packages-and-crates-for-making-libraries-and-executables.md
@@ -0,0 +1,31 @@
+## 包和 crate 用来创建库和二进制项目
+
+> [ch07-01-mod-and-the-filesystem.md](https://github.com/rust-lang/book/blob/master/src/ch07-01-packages-and-crates-for-making-libraries-and-executables.md)
+>
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
+
+让我们聊聊 **模块** 与 *crate*。下面是一个总结:
+
+* *crate* 是一个二进制或库项目。
+* **crate 根**(*crate root*)是一个用来描述如何构建 crate 的文件。
+* 带有 *Cargo.toml* 文件的 **包** 用以描述如何构建一个或多个 crate。一个包中至多可以有一个库项目。
+
+所以当运行 `cargo new` 时是在创建一个包:
+
+```text
+$ cargo new my-project
+ Created binary (application) `my-project` package
+$ ls my-project
+Cargo.toml
+src
+$ ls my-project/src
+main.rs
+```
+
+因为 Cargo 创建了 *Cargo.toml*,这意味着现在我们有了一个包。如果查看 *Cargo.toml* 的内容,会发现并没有提到 *src/main.rs*。然而,Cargo 的约定是如果在代表包的 *Cargo.toml* 的同级目录下包含 *src* 目录且其中包含 *main.rs* 文件的话,Cargo 就知道这个包带有一个与包同名的二进制 crate,且 *src/main.rs* 就是 crate 根。另一个约定如果包目录中包含 *src/lib.rs*,则包带有与其同名的库 crate,且 *src/lib.rs* 是 crate 根。crate 根文件将由 Cargo 传递给 `rustc` 来实际构建库或者二进制项目。
+
+一个包可以带有零个或一个库 crate 和任意多个二进制 crate。一个包中必须带有至少一个(库或者二进制)crate。
+
+如果包同时包含 *src/main.rs* 和 *src/lib.rs*,那么它带有两个 crate:一个库和一个二进制项目,同名。如果只有其中之一,则包将只有一个库或者二进制 crate。包可以带有多个二进制 crate,需将其文件置于 *src/bin* 目录;每个文件将是一个单独的二进制 crate。
+
+接下来让我们讨论模块!
\ No newline at end of file
diff --git a/src/ch07-02-controlling-visibility-with-pub.md b/src/ch07-02-controlling-visibility-with-pub.md
index 19b8d39..c5f7a0d 100644
--- a/src/ch07-02-controlling-visibility-with-pub.md
+++ b/src/ch07-02-controlling-visibility-with-pub.md
@@ -31,7 +31,7 @@ warning: function is never used: `connect`
| |_^
```
-那么为什么会出现这些错误信息呢?毕竟我们构建的是一个库,它的函数的目的是被 **用户** 使用,而不一定要被项目自身使用,所以不应该担心这些 `connect` 函数是未使用的。创建它们的意义就在于被另一个项目而不是被我们自己使用。
+那么为什么会出现这些警告信息呢?毕竟我们构建的是一个库,它的函数的目的是被 **用户** 使用,而不一定要被项目自身使用,所以不应该担心这些 `connect` 函数是未使用的。创建它们的意义就在于被另一个项目而不是被我们自己使用。
为了理解为什么这个程序出现了这些警告,尝试在另一个项目中使用这个 `connect` 库,从外部调用它们。为此,通过创建一个包含这些代码的 *src/main.rs* 文件,在与库 crate 相同的目录创建一个二进制 crate:
@@ -87,7 +87,7 @@ error[E0603]: function `connect` is private
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
```
-非常好!另一个不同的错误!好的,不同的错误信息也是值得庆祝的(可能是程序员被黑的最惨的一次)。新错误表明“函数 `connect` 是私有的”,那么让我们修改 *src/client.rs* 将 `client::connect` 也设为公有:
+非常好!另一个不同的错误!好的,不同的错误信息也是值得庆祝的。新错误表明“函数 `connect` 是私有的”,那么让我们修改 *src/client.rs* 将 `client::connect` 也设为公有:
文件名: src/client.rs
@@ -118,7 +118,7 @@ warning: function is never used: `connect`
编译通过了,关于 `client::connect` 未被使用的警告消失了!
-未被使用的代码并不总是意味着它们需要被设为公有的:如果你 **不** 希望这些函数成为公有 API 的一部分,未被使用的代码警告可能是在提醒你这些代码不再需要并可以安全的删除它们。这也可能是警告你出 bug 了,如果你刚刚不小心删除了库中所有这个函数的调用。
+未被使用的代码并不总是意味着它们需要被设为公有的:如果你 **不** 希望这些函数成为公有 API 的一部分,未被使用的代码警告可能是在提醒你这些代码不再需要并可以安全地删除它们。这也可能是警告你出 bug 了,如果你刚刚不小心删除了库中所有这个函数的调用。
当然我们的情况是,**确实** 希望另外两个函数也作为 crate 公有 API 的一部分,所以让我们也将其标记为 `pub` 并去掉剩余的警告。修改 *src/network/mod.rs* 为:
@@ -151,7 +151,7 @@ warning: function is never used: `connect`
| |_^
```
-虽然将 `network::connect` 设为 `pub` 了我们仍然得到了一个未被使用函数的警告。这是因为模块中的函数是公有的,不过函数所在的 `network` 模块却不是公有的。这回我们是自内向外修改库文件的,而 `client::connect` 的时候是自外向内修改的。我们需要修改 *src/lib.rs* 让 `network` 也是公有的,如下:
+虽然将 `network::connect` 设为 `pub` 了,我们仍然得到了一个未被使用函数的警告。这是因为模块中的函数是公有的,不过函数所在的 `network` 模块却不是公有的。这回我们是自内向外修改库文件的,而 `client::connect` 的时候是自外向内修改的。我们需要修改 *src/lib.rs* 让 `network` 也是公有的,如下:
文件名: src/lib.rs
@@ -178,7 +178,7 @@ warning: function is never used: `connect`
### 私有性规则
-总的来说,有如下项的可见性规则:
+总的来说,有如下可见性规则:
1. 如果一个项是公有的,它能被任何父模块访问
2. 如果一个项是私有的,它能被其直接父模块及其任何子模块访问
diff --git a/src/ch07-02-modules-and-use-to-control-scope-and-privacy.md b/src/ch07-02-modules-and-use-to-control-scope-and-privacy.md
new file mode 100644
index 0000000..e722319
--- /dev/null
+++ b/src/ch07-02-modules-and-use-to-control-scope-and-privacy.md
@@ -0,0 +1,701 @@
+## 模块系统用来控制作用域和私有性
+
+> [ch07-01-mod-and-the-filesystem.md](https://github.com/rust-lang/book/blob/master/src/ch07-02-modules-and-use-to-control-scope-and-privacy.md)
+>
+> commit 820ac357f6cf0e866e5a8e7a9c57dd3e17e9f8ca
+
+Rust 的此部分功能通常被引用为 “模块系统”(“the module system”),不过其包括了一些除模块之外的功能。在本部分我们会讨论:
+
+* 模块,一个组织代码和控制路径私有性的方式
+* 路径,一个命名项(item)的方式
+* `use` 关键字用来将路径引入作用域
+* `pub` 关键字使项变为公有
+* `as` 关键字用于将项引入作用域时进行重命名
+* 使用外部包
+* 嵌套路径用来消除大量的 `use` 语句
+* 使用 glob 运算符将模块的所有内容引入作用域
+* 如何将不同模块分割到单独的文件中
+
+首先讲讲模块。模块允许我们将代码组织起来。示例 7-1 是一个例子,这些代码定义了名为 `sound` 的模块,其包含名为 `guitar` 的函数。
+
+文件名: src/main.rs
+
+```rust
+mod sound {
+ fn guitar() {
+ // 函数体
+ }
+}
+
+fn main() {
+
+}
+```
+
+示例 7-1: 包含 `guitar` 函数和 `main` 函数的 `sound` 模块
+
+这里定义了两个函数,`guitar` 和 `main`。`guitar` 函数定义于 `mod` 块中。这个块定义了 `sound` 模块。
+
+为了将代码组织到模块层次体系中,可以将模块嵌套进其他模块,如示例 7-2 所示:
+
+文件名: src/main.rs
+
+```rust
+mod sound {
+ mod instrument {
+ mod woodwind {
+ fn clarinet() {
+ // 函数体
+ }
+ }
+ }
+
+ mod voice {
+
+ }
+}
+
+fn main() {
+
+}
+```
+
+示例 7-2: 模块中的模块
+
+在这个例子中,我们像示例 7-1 一样定义了 `sound` 模块。接着在 `sound` 模块中定义了 `instrument` 和 `voice` 模块。`instrument` 模块中定义了另一个模块 `woodwind`,这个模块包含一个函数 `clarinet`。
+
+在 “包和 crate 用来创建库和二进制项目” 部分提到 *src/main.rs* 和 *src/lib.rs* 被称为 **crate 根**。他们被称为 crate 根是因为这两个文件在 crate 模块树的根组成了名为 `crate` 模块。所以示例 7-2 中,有如示例 7-3 所示的模块树:
+
+```text
+crate
+ └── sound
+ └── instrument
+ └── woodwind
+ └── voice
+```
+
+示例 7-3: 示例 7-2 中代码的模块树
+
+这个树展示了模块如何嵌套在其他模块中(比如 `woodwind` 嵌套在 `instrument` 中)以及模块如何作为其他模块的子模块的(`instrument` 和 `voice` 都定义在 `sound` 中)。整个模块树都位于名为 `crate` 这个隐式模块的根下。
+
+这个树可能会令你想起电脑上文件系统的目录树;这是一个非常恰当的比喻!就像文件系统的目录,将代码放入任意模块也将创建对应的组织结构体。另一个相似点是为了引用文件系统或模块树中的项,需要使用 **路径**(*path*)。
+
+### 路径用来引用模块树中的项
+
+如果想要调用函数,需要知道其 **路径**。“路径” 是 “名称”(“name”) 的同义词,不过它用于文件系统语境。另外,函数、结构体和其他项可能会有多个指向相同项的路径,所以 “名称” 这个概念不太准确。
+
+**路径** 可以有两种形式:
+
+* **绝对路径**(*absolute path*)从 crate 根开始,以 crate 名或者字面值 `crate` 开头。
+* **相对路径**(*relative path*)从当前模块开始,以 `self`、`super` 或当前模块的标识符开头。
+
+绝对路径和相对路径都后跟一个或多个由双冒号(`::`)分割的标识符。
+
+如何在示例 7-2 的 `main` 函数中调用 `clarinet` 函数呢?也就是说,`clarinet` 函数的路径是什么呢?在示例 7-4 中稍微简化了代码,移除了一些模块,并展示了两种在 `main` 中调用 `clarinet` 函数的方式。这个例子还不能编译,我们会解释为什么。
+
+文件名: src/main.rs
+
+```rust,ignore,does_not_compile
+mod sound {
+ mod instrument {
+ fn clarinet() {
+ // 函数体
+ }
+ }
+}
+
+fn main() {
+ // 绝对路径
+ crate::sound::instrument::clarinet();
+
+ // Relative path
+ sound::instrument::clarinet();
+}
+```
+
+示例 7-4: 在简化的模块树中,分别使用绝对路径和相对路径在 `main` 中调用 `clarinet` 函数
+
+第一种从 `main` 函数中调用 `clarinet` 函数的方式使用绝对路径。因为 `clarinet` 与 `main` 定义于同一 crate 中,我们使用 `crate` 关键字来开始绝对路径。接着包含每一个模块直到 `clarinet`。这类似于指定 `/sound/instrument/clarinet` 来运行电脑上这个位置的程序;使用 `crate` 从 crate 根开始就类似于在 shell 中使用 `/` 从文件系统根开始。
+
+第二种从 `main` 函数中调用 `clarinet` 函数的方式使用相对路径。该路径以 `sound` 开始,它是定义于与 `main` 函数相同模块树级别的模块。这类似于指定 `sound/instrument/clarinet` 来运行电脑上这个位置的程序;以名称开头意味着路径是相对的。
+
+示例 7-4 提到了它并不能编译,让我们尝试编译并看看为什么不行!示例 7-5 展示了错误。
+
+```text
+$ cargo build
+ Compiling sampleproject v0.1.0 (file:///projects/sampleproject)
+error[E0603]: module `instrument` is private
+ --> src/main.rs:11:19
+ |
+11 | crate::sound::instrument::clarinet();
+ | ^^^^^^^^^^
+
+error[E0603]: module `instrument` is private
+ --> src/main.rs:14:12
+ |
+14 | sound::instrument::clarinet();
+ | ^^^^^^^^^^
+```
+
+示例 7-5: 构建示例 7-4 出现的编译器错误
+
+错误信息说 `instrument` 模块是私有的。可以看到 `instrument` 模块和 `clarinet` 函数的路径是正确的,不过 Rust 不让我们使用,因为他们是私有的。现在是学习 `pub` 关键字的时候了!
+
+### 模块作为私有性的边界
+
+之前我们讨论到模块的语法和组织代码的用途。Rust 采用模块还有另一个原因:模块是 Rust 中的 **私有性边界**(*privacy boundary*)。如果你希望函数或结构体是私有的,将其放入模块。私有性规则有如下:
+
+* 所有项(函数、方法、结构体、枚举、模块和常量)默认是私有的。
+* 可以使用 `pub` 关键字使项变为公有。
+* 不允许使用定义于当前模块的子模块中的私有代码。
+* 允许使用任何定义于父模块或当前模块中的代码。
+
+换句话说,对于没有 `pub` 关键字的项,当你从当前模块向 “下” 看时是私有的,不过当你向 “上” 看时是公有的。再一次想象一下文件系统:如果你没有某个目录的权限,则无法从父目录中查看其内容。如果有该目录的权限,则可以查看其中的目录和任何父目录。
+
+### 使用 `pub` 关键字使项变为公有
+
+示例 7-5 中的错误说 `instrument` 模块是私有的。让我们使用 `pub` 关键字标记 `instrument` 模块使其可以在 `main` 函数中使用。这些改变如示例 7-6 所示,它仍然不能编译,不过会产生一个不同的错误:
+
+文件名: src/main.rs
+
+```rust,ignore,does_not_compile
+mod sound {
+ pub mod instrument {
+ fn clarinet() {
+ // 函数体
+ }
+ }
+}
+
+fn main() {
+ // Absolute path
+ crate::sound::instrument::clarinet();
+
+ // Relative path
+ sound::instrument::clarinet();
+}
+```
+
+示例 7-6: 将 `instrument` 模块声明为 `pub` 以便可以在 `main` 中使用
+
+在 `mod instrument` 之前增加 `pub` 关键字使得模块变为公有。通过这个改变如果允许访问 `sound` 的话,我们就可以访问 `instrument`。`instrument` 的内容仍然是私有的;使得模块公有并不使其内容也是公有的。模块上的 `pub` 关键字允许其父模块引用它。
+
+不过示例 7-6 中的代码仍然产生错误,如示例 7-7 所示:
+
+```text
+$ cargo build
+ Compiling sampleproject v0.1.0 (file:///projects/sampleproject)
+error[E0603]: function `clarinet` is private
+ --> src/main.rs:11:31
+ |
+11 | crate::sound::instrument::clarinet();
+ | ^^^^^^^^
+
+error[E0603]: function `clarinet` is private
+ --> src/main.rs:14:24
+ |
+14 | sound::instrument::clarinet();
+ | ^^^^^^^^
+```
+
+示例 7-7: 构建示例 7-6 时产生的编译器错误
+
+现在的错误表明 `clarinet` 函数是私有的。私有性规则适用于结构体、枚举、函数和方法以及模块。
+
+在 `clarinet` 函数前增加 `pub` 关键字使其变为公有,如示例 7-8 所示:
+
+文件名: src/main.rs
+
+```rust
+mod sound {
+ pub mod instrument {
+ pub fn clarinet() {
+ // 函数体
+ }
+ }
+}
+
+fn main() {
+ // 绝对路径
+ crate::sound::instrument::clarinet();
+
+ // 相对路径
+ sound::instrument::clarinet();
+}
+```
+
+示例 7-8: 在 `mod
+instrument` 和 `fn clarinet` 之前都增加 `pub` 关键字使我们可以在 `main` 中调用此函数
+
+现在可以编译了!让我们看看绝对路径和相对路径再次检查为什么增加 `pub` 关键字使得我们可以在 `main` 中调用这些路径。
+
+在绝对路径的情况下,我们从 `crate`,也就是 crate 根开始。从这开始有 `sound`,这是一个定义于 crate 根中的模块。`sound` 模块不是公有的,不过因为 `main` 函数与 `sound` 定义于同一模块中,可以从 `main` 中引用 `sound`。接下来是 `instrument`,这个模块标记为 `pub`。我们可以访问 `instrument` 的父模块,所以可以访问 `instrument`。最后,`clarinet` 函数被标记为 `pub` 所以可以访问其父模块,所以这个函数调用是有效的!
+
+在相对路径的情况下,其逻辑与绝对路径相同,除了第一步。不同于从 crate 根开始,路径从 `sound` 开始。`sound` 模块与 `main` 定义于同一模块,所以从 `main` 所在模块开始定义的路径是有效的。接下来因为 `instrument` 和 `clarinet` 被标记为 `pub`,路径其余的部分也是有效的,因此函数调用也是有效的!
+
+### 使用 `super` 开始相对路径
+
+也可以使用 `super` 开头来构建相对路径。这么做类似于文件系统中以 `..` 开头:该路径从 **父** 模块开始而不是当前模块。这在例如示例 7-9 这样的情况下有用处,在这里 `clarinet` 函数通过指定以 `super` 开头的路径调用 `breathe_in` 函数:
+
+文件名: src/lib.rs
+
+```rust
+# fn main() {}
+#
+mod instrument {
+ fn clarinet() {
+ super::breathe_in();
+ }
+}
+
+fn breathe_in() {
+ // 函数体
+}
+```
+
+示例 7-9: 使用以 `super` 开头的路径从父目录开始调用函数
+
+`clarinet` 函数位于 `instrument` 模块中,所以可以使用 `super` 进入 `instrument` 的父模块,也就是根 `crate`。从这里可以找到 `breathe_in`。成功!
+
+你可能想要使用 `super` 开头的相对路而不是以 `crate` 开头的绝对路径的原因是 `super` 可能会使修改有着不同模块层级结构的代码变得更容易,如果定义项和调用项的代码被一同移动的话。例如,如果我们决定将 `instrument` 模块和 `breathe_in` 函数放入 `sound` 模块中,这时我们只需增加 `sound` 模块即可,如示例 7-10 所示。
+
+文件名: src/lib.rs
+
+```rust
+mod sound {
+ mod instrument {
+ fn clarinet() {
+ super::breathe_in();
+ }
+ }
+
+ fn breathe_in() {
+ // Function body code goes here
+ }
+}
+```
+
+示例 7-10: 增加一个名为 `sound` 的父模块并不影响相对路径 `super::breathe_in`
+
+示例 7-10 在 `clarinet` 函数中调用 `super::breathe_in` 将如示例 7-9 一样继续有效,无需更新路径。如果在 `clarinet` 函数不使用 `super::breathe_in` 而是使用 `crate::breathe_in` 的话,当增加父模块 `sound` 后,则需要更新 `clarinet` 函数使用 `crate::sound::breathe_in` 路径。使用相对路径可能意味着重新布局模块时需要更少的必要修改。
+
+### 对结构体和枚举使用 `pub`
+
+可以以模块与函数相同的方式来设计公有的结构体和枚举,不过有一些额外的细节。
+
+如果在结构体定义中使用 `pub`,可以使结构体公有。然而结构体的字段仍是私有的。可以在每一个字段的基准上选择其是否公有。在示例 7-11 中定义了一个公有结构体 `plant::Vegetable`,其包含公有的 `name` 字段和私有的 `id` 字段。
+
+文件名: src/main.rs
+
+```rust
+mod plant {
+ pub struct Vegetable {
+ pub name: String,
+ id: i32,
+ }
+
+ impl Vegetable {
+ pub fn new(name: &str) -> Vegetable {
+ Vegetable {
+ name: String::from(name),
+ id: 1,
+ }
+ }
+ }
+}
+
+fn main() {
+ let mut v = plant::Vegetable::new("squash");
+
+ v.name = String::from("butternut squash");
+ println!("{} are delicious", v.name);
+
+ // 如果将如下行取消注释代码将无法编译:
+ // println!("The ID is {}", v.id);
+}
+```
+
+示例 7-11: 结构体带有公有和私有的字段
+
+因为 `plant::Vegetable` 结构体的 `name` 字段使公有的,在 `main` 中可以使用点号读写 `name` 字段。不允许在 `main` 中使用 `id` 字段因为其使私有的。尝试取消注释的行来打印 `id` 字段的值来看看会出现什么错误!另外注意因为 `plant::Vegetable` 有私有字段,需要提供一个公有的关联函数来构建 `Vegetable` 的实例(这里使用了传统的名称 `new`)。如果 `Vegetable` 没有提供这么一个函数,我们就不能在 `main` 中创建 `Vegetable` 的实例,因为在 `main` 中不允许设置私有字段 `id` 的值。
+
+相反,如果有一个公有枚举,其所有成员都是公有。只需在 `enum` 关键词前加上 `pub`,如示例 7-12 所示。
+
+文件名: src/main.rs
+
+```rust
+mod menu {
+ pub enum Appetizer {
+ Soup,
+ Salad,
+ }
+}
+
+fn main() {
+ let order1 = menu::Appetizer::Soup;
+ let order2 = menu::Appetizer::Salad;
+}
+```
+
+示例 7-12: 将枚举设计为公有会使其所有成员公有
+
+因为 `Appetizer` 枚举是公有的,可以在 `main` 中使用 `Soup` and `Salad` 成员。
+
+还有一种使用 `pub` 的场景我们还没有涉及到,而这是我们最后要讲的模块功能:`use` 关键字。我们先单独介绍 `use`,然后展示如何结合使用 `pub` 和 `use`。
+
+### 使用 `use` 关键字将名称引入作用域
+
+你可能考虑过本章很多的函数调用的路径是冗长和重复的。例如示例 7-8 中,当我们选择 `clarinet` 函数的绝对或相对路径时,每次想要调用 `clarinet` 时都不得不也指定 `sound` 和 `instrument`。幸运的是,有一次性将路径引入作用域然后就像调用本地项那样的方法:使用 `use` 关键字。在示例 7-13 中将 `crate::sound::instrument` 模块引入了 `main` 函数的作用域,以便只需指定 `instrument::clarinet` 来调用 `clarinet` 函数。
+
+文件名: src/main.rs
+
+```rust
+mod sound {
+ pub mod instrument {
+ pub fn clarinet() {
+ // 函数体
+ }
+ }
+}
+
+use crate::sound::instrument;
+
+fn main() {
+ instrument::clarinet();
+ instrument::clarinet();
+ instrument::clarinet();
+}
+```
+
+示例 7-13: 使用 `use` 将模块引入作用域并使用绝对路径来缩短在模块中调用项所必须的路径
+
+当指定 `use` 后以 `self` 开头的相对路径在未来可能不是必须的;这是一个开发者正在尽力消除的语言中的不一致。
+
+如果调用项目的代码移动到模块树的不同位置但是定义项目的代码却没有,那么选择使用 `use` 指定绝对路径可以使更新更轻松,这与示例 7-10 中同时移动的情况相对。例如,如果我们决定采用示例 7-13 的代码,将 `main` 函数的行为提取到函数 `clarinet_trio` 中,并将该函数移动到模块 `performance_group` 中,这时 `use` 所指定的路径无需变化,如示例 7-15 所示。
+
+文件名: src/main.rs
+
+```rust
+mod sound {
+ pub mod instrument {
+ pub fn clarinet() {
+ // 函数体
+ }
+ }
+}
+
+mod performance_group {
+ use crate::sound::instrument;
+
+ pub fn clarinet_trio() {
+ instrument::clarinet();
+ instrument::clarinet();
+ instrument::clarinet();
+ }
+}
+
+fn main() {
+ performance_group::clarinet_trio();
+}
+```
+
+示例 7-15: 移动调用项的代码时绝对路径无需移动
+
+相反,如果对示例 7-14 中指定了相对路径的代码做同样的修改,则需要将 `use
+self::sound::instrument` 变为 `use super::sound::instrument`。如果你不确定将来模块树会如何变化,那么选择采用相对或绝对路径是否会减少修改可能全靠猜测,不过本书的作者倾向于通过 `crate` 指定绝对路径,因为定义和调用项的代码更有可能相互独立的在模块树中移动,而不是像示例 7-10 那样一同移动。
+
+### `use` 函数路径使用习惯 VS 其他项
+
+示例 7-13 中,你可能会好奇为什么指定 `use crate::sound::instrument` 接着在 `main` 中调用 `instrument::clarinet`,而不是如示例 7-16 所示的有相同行为的代码:
+
+文件名: src/main.rs
+
+```rust
+mod sound {
+ pub mod instrument {
+ pub fn clarinet() {
+ // 函数体
+ }
+ }
+}
+
+use crate::sound::instrument::clarinet;
+
+fn main() {
+ clarinet();
+ clarinet();
+ clarinet();
+}
+```
+
+示例 7-16: 通过 `use` 将 `clarinet` 函数引入作用域,这是不推荐的
+
+对于函数来说,通过 `use` 指定函数的父模块接着指定父模块来调用方法被认为是习惯用法。这么做而不是像示例 7-16 那样通过 `use` 指定函数的路径,清楚的表明了函数不是本地定义的,同时仍最小化了指定全路径时的重复。
+
+对于结构体、枚举和其它项,通过 `use` 指定项的全路径是习惯用法。例如,示例 7-17 展示了将标准库中 `HashMap` 结构体引入作用域的习惯用法。
+
+文件名: src/main.rs
+
+```rust
+use std::collections::HashMap;
+
+fn main() {
+ let mut map = HashMap::new();
+ map.insert(1, 2);
+}
+```
+
+示例 7-17: 将 `HashMap` 引入作用域的习惯用法
+
+相反,示例 7-18 中的代码将 `HashMap` 的父模块引入作用域不被认为是习惯用法。这个习惯并没有很强制的理由;这是慢慢形成的习惯同时人们习惯于这么读写。
+
+文件名: src/main.rs
+
+```rust
+use std::collections;
+
+fn main() {
+ let mut map = collections::HashMap::new();
+ map.insert(1, 2);
+}
+```
+
+示例 7-18: 将 `HashMap` 引入作用域的非习惯方法
+
+这个习惯的一个例外是如果 `use` 语句会将两个同名的项引入作用域时,这是不允许的。示例 7-19 展示了如何将两个不同父模块的 `Result` 类型引入作用域并引用它们。
+
+文件名: src/lib.rs
+
+```rust
+use std::fmt;
+use std::io;
+
+fn function1() -> fmt::Result {
+# Ok(())
+}
+fn function2() -> io::Result<()> {
+# Ok(())
+}
+```
+
+示例 7-19: 将两个同名类型引入作用域必须使用父模块
+
+因为如果我们指定 `use std::fmt::Result` 和 `use std::io::Result`,则作用域中会有两个 `Result` 类型,Rust 无法知道我们想用哪个 `Result`。尝试这么做并看看编译器错误!
+
+### 通过 `as` 关键字重命名引入作用域的类型
+
+将两个同名类型引入同一作用域这个问题还有另一个解决办法:可以通过在 `use` 后加上 `as` 和一个新名称来为此类型指定一个新的本地名称。示例 7-20 展示了另一个编写示例 7-19 中代码的方法,通过 `as` 重命名了其中一个 `Result` 类型。
+
+文件名: src/lib.rs
+
+```rust
+use std::fmt::Result;
+use std::io::Result as IoResult;
+
+fn function1() -> Result {
+# Ok(())
+}
+fn function2() -> IoResult<()> {
+# Ok(())
+}
+```
+
+示例 7-20: 通过 `as` 关键字重命名引入作用域的类型
+
+在第二个 `use` 语句中,我们选择 `IoResult` 作为 `std::io::Result` 的新名称,它与从 `std::fmt` 引入作用域的 `Result` 并不冲突。这样做也被认为是惯用的;示例 7-19 还是示例 7-20 全看你的选择。
+
+### 通过 `pub use` 重导出名称
+
+当使用 `use` 关键字将名称导入作用域时,在新作用域中可用的名称是私有的。如果希望调用你编写的代码的代码能够像你一样在其自己的作用域内引用这些类型,可以结合 `pub` 和 `use`。这个技术被称为 “重导出”(*re-exporting*),因为这样做将项引入作用域并同时使其可供其他代码引入自己的作用域。
+
+例如,示例 7-21 展示了示例 7-15 中的代码将 `performance_group` 的 `use` 变为 `pub use` 的版本。
+
+文件名: src/main.rs
+
+```rust
+mod sound {
+ pub mod instrument {
+ pub fn clarinet() {
+ // 函数体
+ }
+ }
+}
+
+mod performance_group {
+ pub use crate::sound::instrument;
+
+ pub fn clarinet_trio() {
+ instrument::clarinet();
+ instrument::clarinet();
+ instrument::clarinet();
+ }
+}
+
+fn main() {
+ performance_group::clarinet_trio();
+ performance_group::instrument::clarinet();
+}
+```
+
+示例 7-21: 通过 `pub use` 使名称可引入任何代码的作用域中
+
+通过 `pub use`,现在 `main` 函数可以通过新路径 `performance_group::instrument::clarinet` 来调用 `clarinet` 函数。如果没有指定 `pub use`,`clarinet_trio` 函数可以在其作用域中调用 `instrument::clarinet` 但 `main` 则不允许使用这个新路径。
+
+### 使用外部包
+
+在第二章中我们编写了一个猜猜看游戏。那个项目使用了一个外部包,`rand`,来生成随机数。为了在项目中使用 `rand`,在 *Cargo.toml* 中加入了如下行:
+
+文件名: Cargo.toml
+
+```toml
+[dependencies]
+rand = "0.5.5"
+```
+
+在 *Cargo.toml* 中加入 `rand` 依赖告诉了 Cargo 要从 *https://crates.io* 下载 `rand` 和其依赖,并使其可在项目代码中使用。
+
+接着,为了将 `rand` 定义引入项目包的作用域,加入一行 `use`,它以 `rand` 包名开头并列出了需要引入作用域的项。回忆一下第二章的 “生成一个随机数” 部分,我们曾将 `Rng` trait 引入作用域并调用了 `rand::thread_rng` 函数:
+
+```rust,ignore
+use rand::Rng;
+
+fn main() {
+ let secret_number = rand::thread_rng().gen_range(1, 101);
+}
+```
+
+*https://crates.io* 上有很多社区成员发布的包,将其引入你自己的项目涉及到相同的步骤:在 *Cargo.toml* 列出它们并通过 `use` 将其中定义的项引入项目包的作用域中。
+
+注意标准库(`std`)对于你的包来说也是外部 crate。因为标准库随 Rust 语言一同分发,无需修改 *Cargo.toml* 来引入 `std`,不过需要通过 `use` 将标准库中定义的项引入项目包的作用域中来引用它们,比如 `HashMap`:
+
+```rust
+use std::collections::HashMap;
+```
+
+这是一个以标注库 crate 名 `std` 开头的绝对路径。
+
+### 嵌套路径来消除大量的 `use` 行
+
+当需要引入很多定义于相同包或相同模块的项时,为每一项单独列出一行会占用源码很大的空间。例如猜猜看章节示例 2-4 中有两行 `use` 语句都从 `std` 引入项到作用域:
+
+文件名: src/main.rs
+
+```rust
+use std::cmp::Ordering;
+use std::io;
+// ---snip---
+```
+
+可以使用嵌套的路径将同样的项在一行中引入而不是两行,这么做需要指定路径的相同部分,接着是两个冒号,接着是大括号中的各自不同的路径部分,如示例 7-22 所示。
+
+文件名: src/main.rs
+
+```rust
+use std::{cmp::Ordering, io};
+// ---snip---
+```
+
+示例 7-22: 指定嵌套的路径在一行中将多个带有相同前缀的项引入作用域
+
+在从相同包或模块中引入很多项的程序中,使用嵌套路径显著减少所需的单独 `use` 语句!
+
+也可以剔除掉完全包含在另一个路径中的路径。例如,示例 7-23 中展示了两个 `use` 语句:一个将 `std::io` 引入作用域,另一个将 `std::io::Write` 引入作用域:
+
+文件名: src/lib.rs
+
+```rust
+use std::io;
+use std::io::Write;
+```
+
+示例 7-23: 通过两行 `use` 语句引入两个路径,其中一个是另一个的子路径
+
+两个路径的相同部分是 `std::io`,这正是第一个路径。为了在一行 `use` 语句中引入这两个路径,可以在嵌套路径中使用 `self`,如示例 7-24 所示。
+
+文件名: src/lib.rs
+
+```rust
+use std::io::{self, Write};
+```
+
+示例 7-24: 将示例 7-23 中部分重复的路径合并为一个 `use` 语句
+
+这将 `std::io` 和 `std::io::Write` 同时引入作用域。
+
+### 通过 glob 运算符将所有的公有定义引入作用域
+
+如果希望将一个路径下 **所有** 公有项引入作用域,可以指定路径后跟 `*`,glob 运算符:
+
+```rust
+use std::collections::*;
+```
+
+这个 `use` 语句将 `std::collections` 中定义的所有公有项引入当前作用域。
+
+使用 glob 运算符时请多加小心!如此难以推导作用域中有什么名称和它们是在何处定义的。
+
+glob 运算符经常用于测试模块 `tests` 中,这时会将所有内容引入作用域;我们将在第十一章 “如何编写测试” 部分讲解。glob 运算符有时也用于 prelude 模式;查看 [标准库中的文档](https://doc.rust-lang.org/std/prelude/index.html#other-preludes) 了解这个模式的更多细节。
+
+### 将模块分割进不同文件
+
+目前本章所有的例子都在一个文件中定义多个模块。当模块变得更大时,你可能想要将它们的定义移动到一个单独的文件中使代码更容易阅读。
+
+例如从示例 7-8 的代码开始,我们可以通过修改 crate 根文件(这里是 *src/main.rs*)将 `sound` 模块移动到其自己的文件 *src/sound.rs* 中,如示例 7-25 所示。
+
+文件名: src/main.rs
+
+```rust,ignore
+mod sound;
+
+fn main() {
+ // 绝对路径
+ crate::sound::instrument::clarinet();
+
+ // 相对路径
+ sound::instrument::clarinet();
+}
+```
+
+示例 7-25: 声明 `sound` 模块,其内容位于 *src/sound.rs* 文件
+
+而 *src/sound.rs* 中会包含 `sound` 模块的内容,如示例 7-26 所示。
+
+文件名: src/sound.rs
+
+```rust
+pub mod instrument {
+ pub fn clarinet() {
+ // 函数体
+ }
+}
+```
+
+示例 7-26: `sound` 模块中的定义位于 *src/sound.rs* 中
+
+在 `mod sound` 后使用分号而不是代码块告诉 Rust 在另一个与模块同名的文件中加载模块的内容。
+
+继续重构我们例子,将 `instrument` 模块也提取到其自己的文件中,修改 *src/sound.rs* 只包含 `instrument` 模块的声明:
+
+文件名: src/sound.rs
+
+```rust
+pub mod instrument;
+```
+
+接着创建 *src/sound* 目录和 *src/sound/instrument.rs* 文件来包含 `instrument` 模块的定义:
+
+文件名: src/sound/instrument.rs
+
+```rust
+pub fn clarinet() {
+ // 函数体
+}
+```
+
+模块树依然保持相同,`main` 中的函数调用也无需修改继续保持有效,即使其定义存在于不同的文件中。这样随着代码增长可以将模块移动到新文件中。
+
+## 总结
+
+Rust 提供了将包组织进 crate、将 crate 组织进模块和通过指定绝对或相对路径从一个模块引用另一个模块中定义的项的方式。可以通过 `use` 语句将路径引入作用域,这样在多次使用时可以使用更短的路径。模块定义的代码默认是私有的,不过可以选择增加 `pub` 关键字使其定义变为公有。
+
+接下来,让我们看看一些标准库提供的集合数据类型,你可以利用它们编写出漂亮整洁的代码。
diff --git a/src/ch08-00-common-collections.md b/src/ch08-00-common-collections.md
index 1048c80..c937fb0 100644
--- a/src/ch08-00-common-collections.md
+++ b/src/ch08-00-common-collections.md
@@ -1,8 +1,8 @@
# 通用集合类型
-> [ch08-00-common-collections.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch08-00-common-collections.md)
+> [ch08-00-common-collections.md](https://github.com/rust-lang/book/blob/master/src/ch08-00-common-collections.md)
>
-> commit 54e81980185fbb1a4cb5a18dce1dc6deeb66b573
+> commit 820ac357f6cf0e866e5a8e7a9c57dd3e17e9f8ca
Rust 标准库中包含一系列被称为 **集合**(*collections*)的非常有用的数据结构。大部分其他数据类型都代表一个特定的值,不过集合可以包含多个值。不同于内建的数组和元组类型,这些集合指向的数据是储存在堆上的,这意味着数据的数量不必在编译时就已知并且可以随着程序的运行增长或缩小。每种集合都有着不同能力和代价,而为所处的场景选择合适的集合则是你将要始终成长的技能。在这一章里,我们将详细的了解三个在 Rust 程序中被广泛使用的集合:
@@ -14,4 +14,4 @@ Rust 标准库中包含一系列被称为 **集合**(*collections*)的非常
[collections]: https://doc.rust-lang.org/std/collections
-我们将讨论如何创建和更新 vector、字符串和哈希 map,以及它们有什么不同。
+我们将讨论如何创建和更新 vector、字符串和哈希 map,以及它们有什么特别之处。
diff --git a/src/ch08-01-vectors.md b/src/ch08-01-vectors.md
index 834d050..5c321a7 100644
--- a/src/ch08-01-vectors.md
+++ b/src/ch08-01-vectors.md
@@ -1,8 +1,8 @@
## vector 用来储存一系列的值
-> [ch08-01-vectors.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch08-01-vectors.md)
+> [ch08-01-vectors.md](https://github.com/rust-lang/book/blob/master/src/ch08-01-vectors.md)
>
-> commit 550c8ea6f74060ff1f7b67e7e1878c4da121682d
+> commit a86c1d315789b3ca13b20d50ad5005c62bdd9e37
我们要讲到的第一个类型是 `Vec`,也被称为 *vector*。vector 允许我们在一个单独的数据结构中储存多于一个的值,它在内存中彼此相邻地排列所有的值。vector 只能储存相同类型的值。它们在拥有一系列项的场景下非常实用,例如文件中的文本行或是购物车中商品的价格。
@@ -16,7 +16,7 @@ let v: Vec = Vec::new();
示例 8-1:新建一个空的 vector 来储存 `i32` 类型的值
-注意这里我们增加了一个类型注解。因为没有向这个 vector 中插入任何值,Rust 并不知道我们想要储存什么类型的元素。这是一个非常重要的点。vector 是用泛型实现的,第十章会涉及到如何对你自己的类型使用它们。现在,所有你需要知道的就是 `Vec` 是一个由标准库提供的类型,它可以存放任何类型,而当 `Vec` 存放某个特定类型时,那个类型位于尖括号中。这里我们告诉 Rust `v` 这个 `Vec` 将存放 `i32` 类型的元素。
+注意这里我们增加了一个类型注解。因为没有向这个 vector 中插入任何值,Rust 并不知道我们想要储存什么类型的元素。这是一个非常重要的点。vector 是用泛型实现的,第十章会涉及到如何对你自己的类型使用它们。现在,所有你需要知道的就是 `Vec` 是一个由标准库提供的类型,它可以存放任何类型,而当 `Vec` 存放某个特定类型时,那个类型位于尖括号中。在示例 8-1 中,我们告诉 Rust `v` 这个 `Vec` 将存放 `i32` 类型的元素。
在更实际的代码中,一旦插入值 Rust 就可以推断出想要存放的类型,所以你很少会需要这些类型注解。更常见的做法是使用初始值来创建一个 `Vec`,而且为了方便 Rust 提供了 `vec!` 宏。这个宏会根据我们提供的值来创建一个新的 `Vec`。示例 8-2 新建一个拥有值 `1`、`2` 和 `3` 的 `Vec`:
@@ -53,9 +53,9 @@ v.push(8);
{
let v = vec![1, 2, 3, 4];
- // do stuff with v
+ // 处理变量 v
-} // <- v goes out of scope and is freed here
+} // <- 这里 v 离开作用域并被丢弃
```
示例 8-4:展示 vector 和其元素于何处被丢弃
@@ -72,7 +72,12 @@ v.push(8);
let v = vec![1, 2, 3, 4, 5];
let third: &i32 = &v[2];
-let third: Option<&i32> = v.get(2);
+println!("The third element is {}", third);
+
+match v.get(2) {
+ Some(third) => println!("The third element is {}", third),
+ None => println!("There is no third element."),
+}
```
列表 8-5:使用索引语法或 `get` 方法来访问 vector 中的项
@@ -81,7 +86,7 @@ let third: Option<&i32> = v.get(2);
Rust 有两个引用元素的方法的原因是程序可以选择如何处理当索引值在 vector 中没有对应值的情况。作为一个例子,让我们看看如果有一个有五个元素的 vector 接着尝试访问索引为 100 的元素时程序会如何处理,如示例 8-6 所示:
-```rust,should_panic
+```rust,should_panic,panics
let v = vec![1, 2, 3, 4, 5];
let does_not_exist = &v[100];
@@ -90,20 +95,20 @@ let does_not_exist = v.get(100);
示例 8-6:尝试访问一个包含 5 个元素的 vector 的索引 100 处的元素
-当运行这段代码,你会发现对于第一个 `[]` 方法,当引用一个不存在的元素时 Rust 会造成 `panic!`。这个方法更适合当程序认为尝试访问超过 vector 结尾的元素是一个严重错误的情况,这时应该使程序崩溃。
+当运行这段代码,你会发现对于第一个 `[]` 方法,当引用一个不存在的元素时 Rust 会造成 panic。这个方法更适合当程序认为尝试访问超过 vector 结尾的元素是一个严重错误的情况,这时应该使程序崩溃。
当 `get` 方法被传递了一个数组外的索引时,它不会 panic 而是返回 `None`。当偶尔出现超过 vector 范围的访问属于正常情况的时候可以考虑使用它。接着你的代码可以有处理 `Some(&element)` 或 `None` 的逻辑,如第六章讨论的那样。例如,索引可能来源于用户输入的数字。如果它们不慎输入了一个过大的数字那么程序就会得到 `None` 值,你可以告诉用户当前 vector 元素的数量并再请求它们输入一个有效的值。这就比因为输入错误而使程序崩溃要友好的多!
-#### 无效引用
+一旦程序获取了一个有效的引用,借用检查器将会执行所有权和借用规则(第四章讲到)来确保 vector 内容的这个引用和任何其他引用保持有效。回忆一下不能在相同作用域中同时存在可变和不可变引用的规则。这个规则适用于示例 8-7,当我们获取了 vector 的第一个元素的不可变引用并尝试在 vector 末尾增加一个元素的时候,这是行不通的:
-一旦程序获取了一个有效的引用,借用检查器将会执行第四章讲到的所有权和借用规则来确保 vector 内容的这个引用和任何其他引用保持有效。回忆一下不能在相同作用域中同时存在可变和不可变引用的规则。这个规则适用于示例 8-7,当我们获取了 vector 的第一个元素的不可变引用并尝试在 vector 末尾增加一个元素的时候,这是行不通的:
-
-```rust,ignore
+```rust,ignore,does_not_compile
let mut v = vec![1, 2, 3, 4, 5];
let first = &v[0];
v.push(6);
+
+println!("The first element is: {}", first);
```
示例 8-7:在拥有 vector 中项的引用的同时向其增加一个元素
@@ -112,19 +117,19 @@ v.push(6);
```text
error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
- -->
- |
-4 | let first = &v[0];
- | - immutable borrow occurs here
-5 |
-6 | v.push(6);
- | ^ mutable borrow occurs here
-7 |
-8 | }
- | - immutable borrow ends here
+ --> src/main.rs:10:5
+ |
+8 | let first = &v[0];
+ | - immutable borrow occurs here
+9 |
+10 | v.push(6);
+ | ^^^^^^^^^ mutable borrow occurs here
+11 |
+12 | println!("The first element is: {}", first);
+ | ----- borrow later used here
```
-示例 8-7 中的代码看起来应该能够运行:为什么第一个元素的引用会关心 vector 结尾的变化?不能这么做的原因是由于 vector 的工作方式。在 vector 的结尾增加新元素时,在没有足够空间将所有所有元素依次相邻存放的情况下,可能会要求分配新内存并将老的元素拷贝到新的空间中。这时,第一个元素的引用就指向了被释放的内存。借用规则阻止程序陷入这种状况。
+示例 8-7 中的代码看起来应该能够运行:为什么第一个元素的引用会关心 vector 结尾的变化?不能这么做的原因是由于 vector 的工作方式:在 vector 的结尾增加新元素时,在没有足够空间将所有所有元素依次相邻存放的情况下,可能会要求分配新内存并将老的元素拷贝到新的空间中。这时,第一个元素的引用就指向了被释放的内存。借用规则阻止程序陷入这种状况。
> 注意:关于 `Vec` 类型的更多实现细节,在 *https://doc.rust-lang.org/stable/nomicon/vec.html* 查看 “The Nomicon”
@@ -152,7 +157,7 @@ for i in &mut v {
示例8-9:遍历 vector 中元素的可变引用
-为了修改可变引用所指向的值,在使用 `+=` 运算符之前必须使用解引用运算符(`*`)获取 `i` 中的值。
+为了修改可变引用所指向的值,在使用 `+=` 运算符之前必须使用解引用运算符(`*`)获取 `i` 中的值。第十五章会详细介绍 `*`。
### 使用枚举来储存多种类型
diff --git a/src/ch08-02-strings.md b/src/ch08-02-strings.md
index 570e870..dc46cba 100644
--- a/src/ch08-02-strings.md
+++ b/src/ch08-02-strings.md
@@ -1,10 +1,10 @@
## 使用字符串存储 UTF-8 编码的文本
-> [ch08-02-strings.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch08-02-strings.md)
+> [ch08-02-strings.md](https://github.com/rust-lang/book/blob/master/src/ch08-02-strings.md)
>
-> commit d0e83220e083ef87880e6a04f030b90c9af9385b
+> commit a86c1d315789b3ca13b20d50ad5005c62bdd9e37
-第四章已经讲过一些字符串的内容,不过现在让我们更深入地了解它。字符串是新晋 Rustacean 们通常会被困住的领域,这是由于三方面内容的结合:Rust 倾向于确保暴露出可能的错误,字符串是比很多程序员所想象的要更为复杂的数据结构,以及 UTF-8。所有这些结合起来对于来自其他语言背景的程序员就可能显得很困难了。
+第四章已经讲过一些字符串的内容,不过现在让我们更深入地了解它。字符串是新晋 Rustacean 们通常会被困住的领域,这是由于三方面理由的结合:Rust 倾向于确保暴露出可能的错误,字符串是比很多程序员所想象的要更为复杂的数据结构,以及 UTF-8。所有这些要素结合起来对于来自其他语言背景的程序员就可能显得很困难了。
在集合章节中讨论字符串的原因是,字符串就是作为字节的集合外加一些方法实现的,当这些字节被解释为文本时,这些方法提供了实用的功能。在这一部分,我们会讲到 `String` 中那些任何集合类型都有的操作,比如创建、更新和读取。也会讨论 `String` 与其他集合不一样的地方,例如索引` String` 是很复杂的,由于人和计算机理解 `String` 数据方式的不同。
@@ -26,16 +26,14 @@ let mut s = String::new();
示例 8-11:新建一个空的 `String`
-这新建了一个叫做 `s` 的空的字符串,接着我们可以向其中装载数据。
-
-通常字符串会有初始数据,因为我们希望一开始就有这个字符串。为此,可以使用 `to_string` 方法,它能用于任何实现了 `Display` trait 的类型,字符串字面值也实现了它。示例 8-12 展示了两个例子。
+这新建了一个叫做 `s` 的空的字符串,接着我们可以向其中装载数据。通常字符串会有初始数据,因为我们希望一开始就有这个字符串。为此,可以使用 `to_string` 方法,它能用于任何实现了 `Display` trait 的类型,字符串字面值也实现了它。示例 8-12 展示了两个例子。
```rust
let data = "initial contents";
let s = data.to_string();
-// the method also works on a literal directly:
+// 该方法也可直接用于字符串字面值:
let s = "initial contents".to_string();
```
@@ -71,13 +69,13 @@ let hello = String::from("Hola");
示例 8-14:在字符串中储存不同语言的问候语
-所有这些都是有效的 `String`值。
+所有这些都是有效的 `String` 值。
### 更新字符串
-`String` 的大小可以增长其内容也可以改变,就像可以放入更多数据来改变 `Vec` 的内容一样。另外,`String` 实现了 `+` 运算符作为连接运算符以便于使用。
+`String` 的大小可以增长其内容也可以改变,就像可以放入更多数据来改变 `Vec` 的内容一样。另外,可以方便的使用 `+` 运算符或 `format!` 宏来拼接 `String` 值。
-#### 使用 push 附加字符串
+#### 使用 `push_str` 和 `push` 附加字符串
可以通过 `push_str` 方法来附加字符串 slice,从而使 `String` 变长,如示例 8-15 所示。
@@ -101,7 +99,7 @@ println!("s2 is {}", s2);
如果 `push_str` 方法获取了 `s2` 的所有权,就不能在最后一行打印出其值了。好在代码如我们期望那样工作!
-`push` 方法被定义为获取一个单独的字符作为参数,并附加到 `String` 中。示例 8-17 展示了使用 `push` 方法将字母 l 加入 `String` 的代码。
+`push` 方法被定义为获取一个单独的字符作为参数,并附加到 `String` 中。示例 8-17 展示了使用 `push` 方法将字母 *l* 加入 `String` 的代码。
```rust
let mut s = String::from("lo");
@@ -112,14 +110,14 @@ s.push('l');
执行这些代码之后,`s` 将会包含 “lol”。
-#### 使用 + 运算符或 `format!` 宏连接字符串
+#### 使用 `+` 运算符或 `format!` 宏拼接字符串
通常你会希望将两个已知的字符串合并在一起。一种办法是像这样使用 `+` 运算符,如示例 8-18 所示。
```rust
let s1 = String::from("Hello, ");
let s2 = String::from("world!");
-let s3 = s1 + &s2; // note s1 has been moved here and can no longer be used
+let s3 = s1 + &s2; // 注意 s1 被移动了,不能继续使用
```
示例 8-18:使用 `+` 运算符将两个 `String` 值合并到一个新的 `String` 值中
@@ -132,11 +130,11 @@ fn add(self, s: &str) -> String {
这并不是标准库中实际的签名;标准库中的 `add` 使用泛型定义。这里我们看到的 `add` 的签名使用具体类型代替了泛型,这也正是当使用 `String` 值调用这个方法会发生的。第十章会讨论泛型。这个签名提供了理解 `+` 运算那微妙部分的线索。
-首先,`s2` 使用了 `&`,意味着我们使用第二个字符串的 **引用** 与第一个字符串相加。这是因为 `add` 函数的 `s` 参数:只能将 `&str` 和 `String` 相加,不能将两个 `String` 值相加。不过等一下——正如 `add` 的第二个参数所指定的,`&s2` 的类型是 `&String` 而不是 `&str`。那么为什么示例 8-18 还能编译呢?
+首先,`s2` 使用了 `&`,意味着我们使用第二个字符串的 **引用** 与第一个字符串相加。这是因为 `add` 函数的 `s` 参数:只能将 `&str` 和 `String` 相加,不能将两个 `String` 值相加。不过等一下 —— 正如 `add` 的第二个参数所指定的,`&s2` 的类型是 `&String` 而不是 `&str`。那么为什么示例 8-18 还能编译呢?
-之所以能够在 `add` 调用中使用 `&s2` 是因为 `&String` 可以被 **强转**(*coerced*)成 `&str`——当`add`函数被调用时,Rust 使用了一个被称为 **解引用强制多态**(*deref coercion*)的技术,你可以将其理解为它把 `&s2` 变成了 `&s2[..]`。第十五章会更深入的讨论解引用强制多态。因为 `add` 没有获取参数的所有权,所以 `s2` 在这个操作后仍然是有效的 `String`。
+之所以能够在 `add` 调用中使用 `&s2` 是因为 `&String` 可以被 **强转**(*coerced*)成 `&str`。当`add`函数被调用时,Rust 使用了一个被称为 **解引用强制多态**(*deref coercion*)的技术,你可以将其理解为它把 `&s2` 变成了 `&s2[..]`。第十五章会更深入的讨论解引用强制多态。因为 `add` 没有获取参数的所有权,所以 `s2` 在这个操作后仍然是有效的 `String`。
-其次,可以发现签名中 `add` 获取了 `self` 的所有权,因为 `self` **没有** 使用 `&`。这意味着上面例子中的 `s1` 的所有权将被移动到 `add` 调用中,之后就不再有效。所以虽然 `let s3 = s1 + &s2;` 看起来就像它会复制两个字符串并创建一个新的字符串,而实际上这个语句会获取 `s1` 的所有权,附加上从 `s2` 中拷贝的内容,并返回结果的所有权。换句话说,它看起来好像生成了很多拷贝不过实际上并没有:这个实现比拷贝要更高效。
+其次,可以发现签名中 `add` 获取了 `self` 的所有权,因为 `self` **没有** 使用 `&`。这意味着示例 8-18 中的 `s1` 的所有权将被移动到 `add` 调用中,之后就不再有效。所以虽然 `let s3 = s1 + &s2;` 看起来就像它会复制两个字符串并创建一个新的字符串,而实际上这个语句会获取 `s1` 的所有权,附加上从 `s2` 中拷贝的内容,并返回结果的所有权。换句话说,它看起来好像生成了很多拷贝不过实际上并没有:这个实现比拷贝要更高效。
如果想要级联多个字符串,`+` 的行为就显得笨重了:
@@ -164,7 +162,7 @@ let s = format!("{}-{}-{}", s1, s2, s3);
在很多语言中,通过索引来引用字符串中的单独字符是有效且常见的操作。然而在 Rust 中,如果你尝试使用索引语法访问 `String` 的一部分,会出现一个错误。考虑一下如示例 8-19 中所示的无效代码。
-```rust,ignore
+```rust,ignore,does_not_compile
let s1 = String::from("hello");
let h = s1[0];
```
@@ -193,7 +191,7 @@ error[E0277]: the trait bound `std::string::String: std::ops::Index<{integer}>`
let len = String::from("Hola").len();
```
-在这里,`len` 的值是 4 ,这意味着储存字符串 “Hola” 的 `Vec` 的长度是四个字节:这里每一个字母的 UTF-8 编码都占用一个字节。那下面这个例子又如何呢?(注意这个字符串中的首字母是西里尔字母的 Ze 而不是阿拉伯数字 3 )
+在这里,`len` 的值是 4 ,这意味着储存字符串 “Hola” 的 `Vec` 的长度是四个字节:这里每一个字母的 UTF-8 编码都占用一个字节。那下面这个例子又如何呢?(注意这个字符串中的首字母是西里尔字母的 Ze 而不是阿拉伯数字 3 。)
```rust
let len = String::from("Здравствуйте").len();
@@ -201,7 +199,7 @@ let len = String::from("Здравствуйте").len();
当问及这个字符是多长的时候有人可能会说是 12。然而,Rust 的回答是 24。这是使用 UTF-8 编码 “Здравствуйте” 所需要的字节数,这是因为每个 Unicode 标量值需要两个字节存储。因此一个字符串字节值的索引并不总是对应一个有效的 Unicode 标量值。作为演示,考虑如下无效的 Rust 代码:
-```rust,ignore
+```rust,ignore,does_not_compile
let hello = "Здравствуйте";
let answer = &hello[0];
```
@@ -291,8 +289,6 @@ for b in "नमस्ते".bytes() {
```text
224
164
-168
-224
// --snip--
165
135
diff --git a/src/ch08-03-hash-maps.md b/src/ch08-03-hash-maps.md
index 8213d07..9c86f8f 100644
--- a/src/ch08-03-hash-maps.md
+++ b/src/ch08-03-hash-maps.md
@@ -1,14 +1,14 @@
## 哈希 map 储存键值对
-> [ch08-03-hash-maps.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch08-03-hash-maps.md)
+> [ch08-03-hash-maps.md](https://github.com/rust-lang/book/blob/master/src/ch08-03-hash-maps.md)
>
-> commit c2fd7b2d39c4130dd17bb99c101ac94af83d1a44
+> commit d073ece693e880b69412e645e4eabe99e74e7590
最后介绍的常用集合类型是 **哈希 map**(*hash map*)。`HashMap` 类型储存了一个键类型 `K` 对应一个值类型 `V` 的映射。它通过一个 **哈希函数**(*hashing function*)来实现映射,决定如何将键和值放入内存中。很多编程语言支持这种数据结构,不过通常有不同的名字:哈希、map、对象、哈希表或者关联数组,仅举几例。
-哈希 map 可以用于需要任何类型作为键来寻找数据的情况,而不是像 vector 那样通过索引。例如,在一个游戏中,你可以将每个团队的分数记录到哈希 map 中,其中键是队伍的名字而值是每个队伍的分数。给出一个队名,就能得到它们的得分。
+哈希 map 可以用于需要任何类型作为键来寻找数据的情况,而不是像 vector 那样通过索引。例如,在一个游戏中,你可以将每个团队的分数记录到哈希 map 中,其中键是队伍的名字而值是每个队伍的分数。给出一个队名,就能得到他们的得分。
-本章我们会介绍哈希 map 的基本 API,不过还有更多吸引人的功能隐藏于标准库中 `HashMap` 定义的函数中。请一如既往地查看标准库文档来了解更多信息。
+本章我们会介绍哈希 map 的基本 API,不过还有更多吸引人的功能隐藏于标准库中 `HashMap` 定义的函数中。一如既往请查看标准库文档来了解更多信息。
### 新建一个哈希 map
@@ -56,8 +56,8 @@ let field_value = String::from("Blue");
let mut map = HashMap::new();
map.insert(field_name, field_value);
-// field_name and field_value are invalid at this point, try using them and
-// see what compiler error you get!
+// 这里 field_name 和 field_value 不再有效,
+// 尝试使用它们看看会出现什么编译错误!
```
示例 8-22:展示一旦键值对被插入后就为哈希 map 所拥有
@@ -178,7 +178,9 @@ println!("{:?}", map);
### 哈希函数
-`HashMap` 默认使用一种密码学安全的哈希函数,它可以抵抗拒绝服务(Denial of Service, DoS)攻击。然而这并不是可用的最快的算法,不过为了更高的安全性值得付出一些性能的代价。如果性能监测显示此哈希函数非常慢,以致于你无法接受,你可以指定一个不同的 *hasher* 来切换为其它函数。hasher 是一个实现了 `BuildHasher` trait 的类型。第十章会讨论 trait 和如何实现它们。你并不需要从头开始实现你自己的 hasher;crates.io 有其他人分享的实现了许多常用哈希算法的 hasher 的库。
+`HashMap` 默认使用一种 “密码学安全的”(“cryptographically strong” )[^siphash] 哈希函数,它可以抵抗拒绝服务(Denial of Service, DoS)攻击。然而这并不是可用的最快的算法,不过为了更高的安全性值得付出一些性能的代价。如果性能监测显示此哈希函数非常慢,以致于你无法接受,你可以指定一个不同的 *hasher* 来切换为其它函数。hasher 是一个实现了 `BuildHasher` trait 的类型。第十章会讨论 trait 和如何实现它们。你并不需要从头开始实现你自己的 hasher;[crates.io](https://crates.io) 有其他人分享的实现了许多常用哈希算法的 hasher 的库。
+
+[^siphash]: [https://www.131002.net/siphash/siphash.pdf](https://www.131002.net/siphash/siphash.pdf)
## 总结
diff --git a/src/ch09-00-error-handling.md b/src/ch09-00-error-handling.md
index b5e2429..985da6e 100644
--- a/src/ch09-00-error-handling.md
+++ b/src/ch09-00-error-handling.md
@@ -1,11 +1,11 @@
# 错误处理
-> [ch09-00-error-handling.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch09-00-error-handling.md)
+> [ch09-00-error-handling.md](https://github.com/rust-lang/book/blob/master/src/ch09-00-error-handling.md)
>
-> commit 7f0c806e746a133ab344cb2035d31f2a63fb6d79
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
-Rust 对可靠性的执着也延伸到了错误处理。错误对于软件来说是不可避免的,所以 Rust 有很多特性来处理出现错误的情况。在很多情况下,Rust 要求你承认出错的可能性并在编译代码之前就采取行动。通过确保不会只有在将代码部署到生产环境之后才会发现错误来使得程序更可靠。
+Rust 对可靠性的执着也延伸到了错误处理。错误对于软件来说是不可避免的,所以 Rust 有很多特性来处理出现错误的情况。在很多情况下,Rust 要求你承认出错的可能性并在编译代码之前就采取行动。这些要求使得程序更为健壮,它们确保了你会在将代码部署到生产环境之前就发现错误并正确地处理它们!
Rust 将错误组合成两个主要类别:**可恢复错误**(*recoverable*)和 **不可恢复错误**(*unrecoverable*)。可恢复错误通常代表向用户报告错误和重试操作是合理的情况,比如未找到文件。不可恢复错误通常是 bug 的同义词,比如尝试访问超过数组结尾的位置。
-大部分语言并不区分这两类错误,并采用类似异常这样方式统一处理他们。Rust 并没有异常。相反,对于可恢复错误有 `Result` 值,以及 `panic!`,它在遇到不可恢复错误时停止程序执行。这一章会首先介绍 `panic!` 调用,接着会讲到如何返回 `Result`。此外,我们将在决定是尝试从错误中恢复还是停止执行时探讨注意事项。
+大部分语言并不区分这两类错误,并采用类似异常这样方式统一处理他们。Rust 并没有异常。相反,对于可恢复错误有 `Result` 值,以及 `panic!`,它在遇到不可恢复错误时停止程序执行。这一章会首先介绍 `panic!` 调用,接着会讲到如何返回 `Result`。此外,我们将探讨决定是尝试从错误中恢复还是停止执行时的注意事项。
diff --git a/src/ch09-01-unrecoverable-errors-with-panic.md b/src/ch09-01-unrecoverable-errors-with-panic.md
index 679a107..c2a9193 100644
--- a/src/ch09-01-unrecoverable-errors-with-panic.md
+++ b/src/ch09-01-unrecoverable-errors-with-panic.md
@@ -1,14 +1,14 @@
## `panic!` 与不可恢复的错误
-> [ch09-01-unrecoverable-errors-with-panic.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch09-01-unrecoverable-errors-with-panic.md)
+> [ch09-01-unrecoverable-errors-with-panic.md](https://github.com/rust-lang/book/blob/master/src/ch09-01-unrecoverable-errors-with-panic.md)
>
-> commit 609909ec443042399858d1f679b0df1d6d0eba22
+> commit d073ece693e880b69412e645e4eabe99e74e7590
-突然有一天,糟糕的事情发生了,而你对此束手无策。对于这种情况,Rust 有 `panic!`宏。当执行这个宏时,程序会打印出一个错误信息,展开并清理栈数据,然后接着退出。出现这种情况的场景通常是检测到一些类型的 bug 而且程序员并不清楚该如何处理它。
+突然有一天,代码出问题了,而你对此束手无策。对于这种情况,Rust 有 `panic!`宏。当执行这个宏时,程序会打印出一个错误信息,展开并清理栈数据,然后接着退出。出现这种情况的场景通常是检测到一些类型的 bug 而且程序员并不清楚该如何处理它。
-> ### Panic 中的栈展开与终止
+> ### 对应 panic 时的栈展开或终止
>
-> 当出现 `panic!` 时,程序默认会开始 **展开**(*unwinding*),这意味着 Rust 会回溯栈并清理它遇到的每一个函数的数据,不过这个回溯并清理的过程有很多工作。另一种选择是直接 **终止**(*abort*),这会不清理数据就退出程序。那么程序所使用的内存需要由操作系统来清理。如果你需要项目的最终二进制文件越小越好,panic 时通过在 *Cargo.toml* 的 `[profile]` 部分增加 `panic = 'abort'`,可以由展开切换为终止。例如,如果你想要在release模式中 panic 时直接终止:
+> 当出现 panic 时,程序默认会开始 **展开**(*unwinding*),这意味着 Rust 会回溯栈并清理它遇到的每一个函数的数据,不过这个回溯并清理的过程有很多工作。另一种选择是直接 **终止**(*abort*),这会不清理数据就退出程序。那么程序所使用的内存需要由操作系统来清理。如果你需要项目的最终二进制文件越小越好,panic 时通过在 *Cargo.toml* 的 `[profile]` 部分增加 `panic = 'abort'`,可以由展开切换为终止。例如,如果你想要在release模式中 panic 时直接终止:
>
> ```toml
> [profile.release]
@@ -19,7 +19,7 @@
文件名: src/main.rs
-```rust,should_panic
+```rust,should_panic,panics
fn main() {
panic!("crash and burn");
}
@@ -36,7 +36,7 @@ thread 'main' panicked at 'crash and burn', src/main.rs:2:4
note: Run with `RUST_BACKTRACE=1` for a backtrace.
```
-最后三行包含 `panic!` 造成的错误信息。第一行显示了 panic 提供的信息并指明了源码中 panic 出现的位置:*src/main.rs:2:4* 表明这是 *src/main.rs* 文件的第二行第四个字符。
+最后两行包含 `panic!` 调用造成的错误信息。第一行显示了 panic 提供的信息并指明了源码中 panic 出现的位置:*src/main.rs:2:4* 表明这是 *src/main.rs* 文件的第二行第四个字符。
在这个例子中,被指明的那一行是我们代码的一部分,而且查看这一行的话就会发现 `panic!` 宏的调用。在其他情况下,`panic!` 可能会出现在我们的代码调用的代码中。错误信息报告的文件名和行号可能指向别人代码中的 `panic!` 宏调用,而不是我们代码中最终导致 `panic!` 的那一行。可以使用 `panic!` 被调用的函数的 backtrace 来寻找(我们代码中出问题的地方)。下面我们会详细介绍 backtrace 是什么。
@@ -46,7 +46,7 @@ note: Run with `RUST_BACKTRACE=1` for a backtrace.
文件名: src/main.rs
-```rust,should_panic
+```rust,should_panic,panics
fn main() {
let v = vec![1, 2, 3];
@@ -56,7 +56,7 @@ fn main() {
示例 9-1:尝试访问超越 vector 结尾的元素,这会造成 `panic!`
-这里尝试访问 vector 的第一百个元素,不过它只有三个元素。这种情况下 Rust 会 panic。`[]` 应当返回一个元素,不过如果传递了一个无效索引,就没有可供 Rust 返回的正确的元素。
+这里尝试访问 vector 的第一百个元素(这里的索引是 99 因为索引从 0 开始),不过它只有三个元素。这种情况下 Rust 会 panic。`[]` 应当返回一个元素,不过如果传递了一个无效索引,就没有可供 Rust 返回的正确的元素。
这种情况下其他像 C 这样语言会尝试直接提供所要求的值,即便这可能不是你期望的:你会得到任何对应 vector 中这个元素的内存位置的值,甚至是这些内存并不属于 vector 的情况。这被称为 **缓冲区溢出**(*buffer overread*),并可能会导致安全漏洞,比如攻击者可以像这样操作索引来读取储存在数组后面不被允许的数据。
@@ -70,12 +70,11 @@ $ cargo run
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is
99', /checkout/src/liballoc/vec.rs:1555:10
note: Run with `RUST_BACKTRACE=1` for a backtrace.
-error: Process didn't exit successfully: `target/debug/panic` (exit code: 101)
```
-这指向了一个不是我们编写的文件,*libcollections/vec.rs*。这是标准库中 `Vec` 的实现。这是当对 vector `v` 使用 `[]` 时 *libcollections/vec.rs* 中会执行的代码,也是真正出现 `panic!` 的地方。
+这指向了一个不是我们编写的文件,*vec.rs*。这是标准库中 `Vec` 的实现。这是当对 vector `v` 使用 `[]` 时 *vec.rs* 中会执行的代码,也是真正出现 `panic!` 的地方。
-接下来的几行提醒我们可以设置 `RUST_BACKTRACE` 环境变量来得到一个 backtrace *backtrace* 是一个执行到目前位置所有被调用的函数的列表。Rust 的 backtrace 跟其他语言中的一样:阅读 backtrace 的关键是从头开始读直到发现你编写的文件。这就是问题的发源地。这一行往上是你的代码调用的代码;往下则是调用你的代码的代码。这些行可能包含核心 Rust 代码,标准库代码或用到的 crate 代码。让我们尝试获取一个 backtrace:示例 9-2 展示了与你看到类似的输出:
+接下来的几行提醒我们可以设置 `RUST_BACKTRACE` 环境变量来得到一个 backtrace *backtrace* 是一个执行到目前位置所有被调用的函数的列表。Rust 的 backtrace 跟其他语言中的一样:阅读 backtrace 的关键是从头开始读直到发现你编写的文件。这就是问题的发源地。这一行往上是你的代码调用的代码;往下则是调用你的代码的代码。这些行可能包含核心 Rust 代码,标准库代码或用到的 crate 代码。让我们将 `RUST_BACKTRACE` 环境变量设置为任何不是 0 的值来获取 backtrace 看看。示例 9-2 展示了与你看到类似的输出:
```text
$ RUST_BACKTRACE=1 cargo run
@@ -121,8 +120,8 @@ stack backtrace:
示例 9-2:当设置 `RUST_BACKTRACE` 环境变量时 `panic!` 调用所生成的 backtrace 信息
-这里有大量的输出!你实际看到的输出可能因不同的操作系统和 Rust 版本而有所不同。为了获取带有这些信息的 backtrace,必须启用 debug 标识。当不使用 --release 参数运行 cargo build 或 cargo run 时 debug 标识会默认启用,这里便是如此。
+这里有大量的输出!你实际看到的输出可能因不同的操作系统和 Rust 版本而有所不同。为了获取带有这些信息的 backtrace,必须启用 debug 标识。当不使用 `--release` 参数运行 cargo build 或 cargo run 时 debug 标识会默认启用,就像这里一样。
-示例 9-2 的输出中,backtrace 的 11 行指向了我们项目中造成问题的行:*src/main.rs* 的第 4 行。如果你不希望程序 panic,第一个提到我们编写的代码行的位置是你应该开始调查的,以便查明是什么值如何在这个地方引起了 panic。在上面的例子中,我们故意编写会 panic 的代码来演示如何使用 backtrace,修复这个 panic 的方法就是不要尝试在一个只包含三个项的 vector 中请求索引是 100 的元素。当将来你的代码出现了 panic,你需要搞清楚在这特定的场景下代码中执行了什么操作和什么值导致了 panic,以及应当如何处理才能避免这个问题。
+示例 9-2 的输出中,backtrace 的 11 行指向了我们项目中造成问题的行:*src/main.rs* 的第 4 行。如果你不希望程序 panic,第一个提到我们编写的代码行的位置是你应该开始调查的,以便查明是什么值如何在这个地方引起了 panic。在示例 9-1 中,我们故意编写会 panic 的代码来演示如何使用 backtrace,修复这个 panic 的方法就是不要尝试在一个只包含三个项的 vector 中请求索引是 100 的元素。当将来你的代码出现了 panic,你需要搞清楚在这特定的场景下代码中执行了什么操作和什么值导致了 panic,以及应当如何处理才能避免这个问题。
-本章后面的小节“panic! 还是不 panic!”会再次回到 `panic!` 并讲到何时应该及何时不应该使用这个方式。接下来,我们来看看如何使用 `Result` 来从错误中恢复。
+本章后面的小节 “panic! 还是不 panic!”会再次回到 `panic!` 会回到 `panic!` 并讲解何时应该何时不应该使用 `panic!` 来处理错误情况。接下来,我们来看看如何使用 `Result` 来从错误中恢复。
diff --git a/src/ch09-02-recoverable-errors-with-result.md b/src/ch09-02-recoverable-errors-with-result.md
index f8ae13d..a24743d 100644
--- a/src/ch09-02-recoverable-errors-with-result.md
+++ b/src/ch09-02-recoverable-errors-with-result.md
@@ -1,8 +1,8 @@
## `Result` 与可恢复的错误
-> [ch09-02-recoverable-errors-with-result.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch09-02-recoverable-errors-with-result.md)
+> [ch09-02-recoverable-errors-with-result.md](https://github.com/rust-lang/book/blob/master/src/ch09-02-recoverable-errors-with-result.md)
>
-> commit 347a8bf6beaf34cef5c4e82c2171f498f081485e
+> commit db53e2e3cdf77beac853df6f29db4b3b86ea598c
大部分错误并没有严重到需要程序完全停止执行。有时,一个函数会因为一个容易理解并做出反应的原因失败。例如,如果尝试打开一个文件不过由于文件并不存在而失败,此时我们可能想要创建这个文件而不是终止进程。
@@ -33,7 +33,7 @@ fn main() {
示例 9-3:打开文件
-如何知道 `File::open` 返回一个 `Result` 呢?我们可以查看标准库 API 文档,或者可以直接问编译器!如果给 `f` 某个我们知道 **不是** 函数返回值类型的类型注解,接着尝试编译代码,编译器会告诉我们类型不匹配。然后错误信息会告诉我们 `f` 的类型 **应该** 是什么。让我们试试!我们知道 `File::open` 的返回值不是 `u32` 类型的,所以将 `let f` 语句改为如下:
+如何知道 `File::open` 返回一个 `Result` 呢?我们可以查看 [标准库 API 文档](https://doc.rust-lang.org/std/index.html),或者可以直接问编译器!如果给 `f` 某个我们知道 **不是** 函数返回值类型的类型注解,接着尝试编译代码,编译器会告诉我们类型不匹配。然后错误信息会告诉我们 `f` 的类型 **应该** 是什么。让我们试试!我们知道 `File::open` 的返回值不是 `u32` 类型的,所以将 `let f` 语句改为如下:
```rust,ignore
let f: u32 = File::open("hello.txt");
@@ -99,6 +99,9 @@ Os { code: 2, message: "No such file or directory" } }', src/main.rs:9:12
文件名: src/main.rs
+
+
```rust,ignore
use std::fs::File;
use std::io::ErrorKind;
@@ -108,22 +111,12 @@ fn main() {
let f = match f {
Ok(file) => file,
- Err(ref error) if error.kind() == ErrorKind::NotFound => {
- match File::create("hello.txt") {
+ Err(error) => match error.kind() {
+ ErrorKind::NotFound => match File::create("hello.txt") {
Ok(fc) => fc,
- Err(e) => {
- panic!(
- "Tried to create file but there was a problem: {:?}",
- e
- )
- },
- }
- },
- Err(error) => {
- panic!(
- "There was a problem opening the file: {:?}",
- error
- )
+ Err(e) => panic!("Tried to create file but there was a problem: {:?}", e),
+ },
+ other_error => panic!("There was a problem opening the file: {:?}", other_error),
},
};
}
@@ -131,15 +124,36 @@ fn main() {
示例 9-5:使用不同的方式处理不同类型的错误
-`File::open` 返回的 `Err` 成员中的值类型 `io::Error`,它是一个标准库中提供的结构体。这个结构体有一个返回 `io::ErrorKind` 值的 `kind` 方法可供调用。`io::ErrorKind` 是一个标准库提供的枚举,它的成员对应 `io` 操作可能导致的不同错误类型。我们感兴趣的成员是 `ErrorKind::NotFound`,它代表尝试打开的文件并不存在。
+`File::open` 返回的 `Err` 成员中的值类型 `io::Error`,它是一个标准库中提供的结构体。这个结构体有一个返回 `io::ErrorKind` 值的 `kind` 方法可供调用。`io::ErrorKind` 是一个标准库提供的枚举,它的成员对应 `io` 操作可能导致的不同错误类型。我们感兴趣的成员是 `ErrorKind::NotFound`,它代表尝试打开的文件并不存在。所以 `match` 的 `f` 匹配,不过对于 `error.kind()` 还有一个内部 `match`。
+
+我们希望在匹配守卫中检查的条件是 `error.kind()` 的返回值是 `ErrorKind`的 `NotFound` 成员。如果是,则尝试通过 `File::create` 创建文件。然而因为 `File::create` 也可能会失败,还需要增加一个内部 `match` 语句。当文件不能被打开,会打印出一个不同的错误信息。外部 `match` 的最后一个分支保持不变这样对任何除了文件不存在的错误会使程序 panic。
+
+这里有好多 `match`!`match` 确实很强大,不过也非常的基础。第十三章我们会介绍闭包(closure)。`Result` 有很多接受闭包的方法,并采用 `match` 表达式实现。一个更老练的 Rustacean 可能会这么写:
+
+```rust,ignore
+use std::fs::File;
+use std::io::ErrorKind;
-条件 `if error.kind() == ErrorKind::NotFound` 被称作 *match guard*:它是一个进一步完善 `match` 分支模式的额外的条件。这个条件必须为真才能使分支的代码被执行;否则,模式匹配会继续并考虑 `match` 中的下一个分支。模式中的 `ref` 是必须的,这样 `error` 就不会被移动到 guard 条件中而仅仅只是引用它。第十八章会详细解释为什么在模式中使用 `ref` 而不是 `&` 来获取一个引用。简而言之,在模式的上下文中,`&` 匹配一个引用并返回它的值,而 `ref` 匹配一个值并返回一个引用。
+fn main() {
+ let f = File::open("hello.txt").map_err(|error| {
+ if error.kind() == ErrorKind::NotFound {
+ File::create("hello.txt").unwrap_or_else(|error| {
+ panic!("Tried to create file but there was a problem: {:?}", error);
+ })
+ } else {
+ panic!("There was a problem opening the file: {:?}", error);
+ }
+ });
+}
+```
-在 match guard 中我们想要检查的条件是 `error.kind()` 是否是 `ErrorKind` 枚举的 `NotFound` 成员。如果是,尝试用 `File::create` 创建文件。然而 `File::create` 也可能会失败,还需要增加一个内部 `match` 语句。当文件不能被打开,会打印出一个不同的错误信息。外部 `match` 的最后一个分支保持不变这样对任何除了文件不存在的错误会使程序 panic。
+在阅读完第十三章后再回到这个例子,并查看标准库文档 `map_err` 和 `unwrap_or_else` 方法都做了什么操作。还有很多这类方法可以消除大量处理错误时嵌套的 `match` 表达式。
### 失败时 panic 的简写:`unwrap` 和 `expect`
-`match` 能够胜任它的工作,不过它可能有点冗长并且不总是能很好的表明其意图。`Result` 类型定义了很多辅助方法来处理各种情况。其中之一叫做 `unwrap`,它的实现就类似于示例 9-4 中的 `match` 语句。如果 `Result` 值是成员 `Ok`,`unwrap` 会返回 `Ok` 中的值。如果 `Result` 是成员 `Err`,`unwrap` 会为我们调用 `panic!`。
+`match` 能够胜任它的工作,不过它可能有点冗长并且不总是能很好的表明其意图。`Result` 类型定义了很多辅助方法来处理各种情况。其中之一叫做 `unwrap`,它的实现就类似于示例 9-4 中的 `match` 语句。如果 `Result` 值是成员 `Ok`,`unwrap` 会返回 `Ok` 中的值。如果 `Result` 是成员 `Err`,`unwrap` 会为我们调用 `panic!`。这里是一个实践 `unwrap` 的例子:
+
+文件名: src/main.rs
```rust,should_panic
use std::fs::File;
@@ -210,7 +224,7 @@ fn read_username_from_file() -> Result {
示例 9-6:一个函数使用 `match` 将错误返回给代码调用者
-首先让我们看看函数的返回值:`Result`。这意味着函数返回一个 `Result` 类型的值,其中泛型参数 `T` 的具体类型是 `String`,而 `E` 的具体类型是 `io::Error`。如果这个函数没有出任何错误成功返回,函数的调用者会收到一个包含 `String` 的 `Ok` 值————函数从文件中读取到的用户名。如果函数遇到任何错误,函数的调用者会收到一个 `Err` 值,它储存了一个包含更多这个问题相关信息的 `io::Error` 实例。这里选择 `io::Error` 作为函数的返回值是因为它正好是函数体中那两个可能会失败的操作的错误返回值:`File::open` 函数和 `read_to_string` 方法。
+首先让我们看看函数的返回值:`Result`。这意味着函数返回一个 `Result` 类型的值,其中泛型参数 `T` 的具体类型是 `String`,而 `E` 的具体类型是 `io::Error`。如果这个函数没有出任何错误成功返回,函数的调用者会收到一个包含 `String` 的 `Ok` 值 —— 函数从文件中读取到的用户名。如果函数遇到任何错误,函数的调用者会收到一个 `Err` 值,它储存了一个包含更多这个问题相关信息的 `io::Error` 实例。这里选择 `io::Error` 作为函数的返回值是因为它正好是函数体中那两个可能会失败的操作的错误返回值:`File::open` 函数和 `read_to_string` 方法。
函数体以 `File::open` 函数开头。接着使用 `match` 处理返回值 `Result`,类似于示例 9-4 中的 `match`,唯一的区别是当 `Err` 时不再调用 `panic!`,而是提早返回并将 `File::open` 返回的错误值作为函数的错误返回值传递给调用者。如果 `File::open` 成功了,我们将文件句柄储存在变量 `f` 中并继续。
@@ -243,7 +257,7 @@ fn read_username_from_file() -> Result {
`Result` 值之后的 `?` 被定义为与示例 9-6 中定义的处理 `Result` 值的 `match` 表达式有着完全相同的工作方式。如果 `Result` 的值是 `Ok`,这个表达式将会返回 `Ok` 中的值而程序将继续执行。如果值是 `Err`,`Err` 中的值将作为整个函数的返回值,就好像使用了 `return` 关键字一样,这样错误值就被传播给了调用者。
-示例 9-6 中的 `match` 表达式与问号运算符所做的有一点不同:`?` 所使用的错误值被传递给了 `from` 函数,它定义于标准库的 `From` trait 中,其用来将错误从一种类型转换为另一种类型。到问号运算符调用 `from` 函数时,收到的错误类型被转换为定义为当前函数返回的错误类型。这在当一个函数返回一个错误类型来代表所有可能失败的方式时很有用,即使其可能会因很多种原因失败。只要每一个错误类型都实现了 `from` 函数来定义如将其转换为返回的错误类型,问号运算符会自动处理这些转换。
+示例 9-6 中的 `match` 表达式与问号运算符所做的有一点不同:`?` 所使用的错误值被传递给了 `from` 函数,它定义于标准库的 `From` trait 中,其用来将错误从一种类型转换为另一种类型。当 `?` 调用 `from` 函数时,收到的错误类型被转换为定义为当前函数返回的错误类型。这在当一个函数返回一个错误类型来代表所有可能失败的方式时很有用,即使其可能会因很多种原因失败。只要每一个错误类型都实现了 `from` 函数来定义如将其转换为返回的错误类型,`?` 会自动处理这些转换。
在示例 9-7 的上下文中,`File::open` 调用结尾的 `?` 将会把 `Ok` 中的值返回给变量 `f`。如果出现了错误,`?` 会提早返回整个函数并将一些 `Err` 值传播给调用者。同理也适用于 `read_to_string` 调用结尾的 `?`。
@@ -269,6 +283,23 @@ fn read_username_from_file() -> Result {
在 `s` 中创建新的 `String` 被放到了函数开头;这一部分没有变化。我们对 `File::open("hello.txt")?` 的结果直接链式调用了 `read_to_string`,而不再创建变量 `f`。仍然需要 `read_to_string` 调用结尾的 `?`,而且当 `File::open` 和 `read_to_string` 都成功没有失败时返回包含用户名 `s` 的 `Ok` 值。其功能再一次与示例 9-6 和示例 9-7 保持一致,不过这是一个与众不同且更符合工程学的写法。
+说到编写这个函数的不同方法,甚至还有一个更短的写法:
+
+文件名: src/main.rs
+
+```rust
+use std::io;
+use std::fs;
+
+fn read_username_from_file() -> Result {
+ fs::read_to_string("hello.txt")
+}
+```
+
+示例 9-9: 使用 `fs::read_to_string`
+
+将文件读取到一个字符串是相当常见的操作,所以 Rust 提供了名为 `fs::read_to_string` 的函数,它会打开文件、新建一个 `String`、读取文件的内容,并将内容放入 `String`,接着返回它。当然,这样做就没有展示所有这些错误处理的机会了,所以我们最初就选择了艰苦的道路。
+
### `?` 只能被用于返回 `Result` 的函数
`?` 只能被用于返回值类型为 `Result` 的函数,因为他被定义为与示例 9-6 中的 `match` 表达式有着完全相同的工作方式。`match` 的 `return Err(e)` 部分要求返回值类型是 `Result`,所以函数的返回值必须是 `Result` 才能与这个 `return` 相兼容。
@@ -286,20 +317,31 @@ fn main() {
当编译这些代码,会得到如下错误信息:
```text
-error[E0277]: the trait bound `(): std::ops::Try` is not satisfied
+error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `std::ops::Try`)
--> src/main.rs:4:13
|
4 | let f = File::open("hello.txt")?;
- | ------------------------
- | |
- | the `?` operator can only be used in a function that returns
- `Result` (or another type that implements `std::ops::Try`)
- | in this macro invocation
+ | ^^^^^^^^^^^^^^^^^^^^^^^^ cannot use the `?` operator in a function that returns `()`
|
= help: the trait `std::ops::Try` is not implemented for `()`
= note: required by `std::ops::Try::from_error`
```
-错误指出只能在返回 `Result` 的函数中使用问号运算符。在不返回 `Result` 的函数中,当调用其他返回 `Result` 的函数时,需要使用 `match` 或 `Result` 的方法之一来处理,而不能用 `?` 将潜在的错误传播给调用者。
+错误指出只能在返回 `Result` 的函数中使用 `?`。在不返回 `Result` 的函数中,当调用其他返回 `Result` 的函数时,需要使用 `match` 或 `Result` 的方法之一来处理,而不能用 `?` 将潜在的错误传播给代码调用方。
+
+不过 `main` 函数可以返回一个 `Result`:
+
+```rust,ignore
+use std::error::Error;
+use std::fs::File;
+
+fn main() -> Result<(), Box> {
+ let f = File::open("hello.txt")?;
+
+ Ok(())
+}
+```
+
+`Box` 被称为 “trait 对象”(“trait object”),第十七章会介绍。目前可以理解 `Box` 为 “任何类型的错误”。
现在我们讨论过了调用 `panic!` 或返回 `Result` 的细节,是时候回到他们各自适合哪些场景的话题了。
diff --git a/src/ch09-03-to-panic-or-not-to-panic.md b/src/ch09-03-to-panic-or-not-to-panic.md
index 56aed76..a703cc8 100644
--- a/src/ch09-03-to-panic-or-not-to-panic.md
+++ b/src/ch09-03-to-panic-or-not-to-panic.md
@@ -1,12 +1,12 @@
## `panic!` 还是不 `panic!`
-> [ch09-03-to-panic-or-not-to-panic.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch09-03-to-panic-or-not-to-panic.md)
+> [ch09-03-to-panic-or-not-to-panic.md](https://github.com/rust-lang/book/blob/master/src/ch09-03-to-panic-or-not-to-panic.md)
>
-> commit 609909ec443042399858d1f679b0df1d6d0eba22
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
那么,该如何决定何时应该 `panic!` 以及何时应该返回 `Result` 呢?如果代码 panic,就没有恢复的可能。你可以选择对任何错误场景都调用 `panic!`,不管是否有可能恢复,不过这样就是你代替调用者决定了这是不可恢复的。选择返回 `Result` 值的话,就将选择权交给了调用者,而不是代替他们做出决定。调用者可能会选择以符合他们场景的方式尝试恢复,或者也可能干脆就认为 `Err` 是不可恢复的,所以他们也可能会调用 `panic!` 并将可恢复的错误变成了不可恢复的错误。因此返回 `Result` 是定义可能会失败的函数的一个好的默认选择。
-有一些情况 panic 比返回 `Result` 更为合适,不过他们并不常见。让我们讨论一下为何在示例、代码原型和测试中,以及那些人们认为不会失败而编译器不这么看的情况下, panic 是合适的,最后会总结一些在库代码中如何决定是否要 panic 的通用指导原则。
+有一些情况 panic 比返回 `Result` 更为合适,不过他们并不常见。让我们讨论一下为何在示例、代码原型和测试中,以及那些人们认为不会失败而编译器不这么看的情况下, panic 是合适的。章节最后会总结一些在库代码中如何决定是否要 panic 的通用指导原则。
### 示例、代码原型和测试都非常适合 panic
@@ -16,7 +16,7 @@
如果方法调用在测试中失败了,我们希望这个测试都失败,即便这个方法并不是需要测试的功能。因为 `panic!` 是测试如何被标记为失败的,调用 `unwrap` 或 `expect` 就是应该发生的事情。
-### 当你比编译器知道更多的情况
+### 当我们比编译器知道更多的情况
当你有一些其他的逻辑来确保 `Result` 会是 `Ok` 值时,调用 `unwrap` 也是合适的,虽然编译器无法理解这种逻辑。你仍然需要处理一个 `Result` 值:即使在你的特定情况下逻辑上是不可能的,你所调用的任何操作仍然有可能失败。如果通过人工检查代码来确保永远也不会出现 `Err` 值,那么调用 `unwrap` 也是完全可以接受的,这里是一个例子:
@@ -38,14 +38,12 @@ let home: IpAddr = "127.0.0.1".parse().unwrap();
如果别人调用你的代码并传递了一个没有意义的值,最好的情况也许就是 `panic!` 并警告使用你的库的人他的代码中有 bug 以便他能在开发时就修复它。类似的,`panic!` 通常适合调用不能够控制的外部代码时,这时无法修复其返回的无效状态。
-无论代码编写的多么好,当有害状态是预期会出现时,返回 `Result` 仍要比调用 `panic!` 更为合适。这样的例子包括解析器接收到错误数据,或者 HTTP 请求返回一个表明触发了限流的状态。在这些例子中,应该通过返回 `Result` 来表明失败预期是可能的,这样将有害状态向上传播,调用者就可以决定该如何处理这个问题。使用 `panic!` 来处理这些情况就不是最好的选择。
+然而当错误预期会出现时,返回 `Result` 仍要比调用 `panic!` 更为合适。这样的例子包括解析器接收到错误数据,或者 HTTP 请求返回一个表明触发了限流的状态。在这些例子中,应该通过返回 `Result` 来表明失败预期是可能的,这样将有害状态向上传播,调用者就可以决定该如何处理这个问题。使用 `panic!` 来处理这些情况就不是最好的选择。
当代码对值进行操作时,应该首先验证值是有效的,并在其无效时 `panic!`。这主要是出于安全的原因:尝试操作无效数据会暴露代码漏洞,这就是标准库在尝试越界访问数组时会 `panic!` 的主要原因:尝试访问不属于当前数据结构的内存是一个常见的安全隐患。函数通常都遵循 **契约**(*contracts*):他们的行为只有在输入满足特定条件时才能得到保证。当违反契约时 panic 是有道理的,因为这通常代表调用方的 bug,而且这也不是那种你希望调用方必须处理的错误。事实上也没有合理的方式来恢复调用方的代码:调用方的 **程序员** 需要修复其代码。函数的契约,尤其是当违反它会造成 panic 的契约,应该在函数的 API 文档中得到解释。
虽然在所有函数中都拥有许多错误检查是冗长而烦人的。幸运的是,可以利用 Rust 的类型系统(以及编译器的类型检查)为你进行很多检查。如果函数有一个特定类型的参数,可以在知晓编译器已经确保其拥有一个有效值的前提下进行你的代码逻辑。例如,如果你使用了一个不同于 `Option` 的类型,而且程序期望它是 **有值** 的并且不是 **空值**。你的代码无需处理 `Some` 和 `None` 这两种情况,它只会有一种情况就是绝对会有一个值。尝试向函数传递空值的代码甚至根本不能编译,所以你的函数在运行时没有必要判空。另外一个例子是使用像 `u32` 这样的无符号整型,也会确保它永远不为负。
-### 创建自定义类型作为验证
-
让我们使用 Rust 类型系统的思想来进一步确保值的有效性,并尝试创建一个自定义类型以进行验证。回忆一下第二章的猜猜看游戏,我们的代码要求用户猜测一个 1 到 100 之间的数字,在将其与秘密数字做比较之前我们从未验证用户的猜测是位于这两个数字之间的,我们只验证它是否为正。在这种情况下,其影响并不是很严重:“Too high” 或 “Too low” 的输出仍然是正确的。但是这是一个很好的引导用户得出有效猜测的辅助,例如当用户猜测一个超出范围的数字或者输入字母时采取不同的行为。
一种实现方式是将猜测解析成 `i32` 而不仅仅是 `u32`,来默许输入负数,接着检查数字是否在范围内:
@@ -73,15 +71,15 @@ loop {
然而,这并不是一个理想的解决方案:程序只处理 1 到 100 之间的值是绝对不可取的,而且如果有很多函数都有这样的要求,在每个函数中都有这样的检查将是非常冗余的(并可能潜在的影响性能)。
-相反我们可以创建一个新类型来将验证放入创建其实例的函数中,而不是到处重复这些检查。这样就可以安全的在函数签名中使用新类型并相信他们接收到的值。示例 9-9 中展示了一个定义 `Guess` 类型的方法,只有在 `new` 函数接收到 1 到 100 之间的值时才会创建 `Guess` 的实例:
+相反我们可以创建一个新类型来将验证放入创建其实例的函数中,而不是到处重复这些检查。这样就可以安全的在函数签名中使用新类型并相信他们接收到的值。示例 9-10 中展示了一个定义 `Guess` 类型的方法,只有在 `new` 函数接收到 1 到 100 之间的值时才会创建 `Guess` 的实例:
```rust
pub struct Guess {
- value: u32,
+ value: i32,
}
impl Guess {
- pub fn new(value: u32) -> Guess {
+ pub fn new(value: i32) -> Guess {
if value < 1 || value > 100 {
panic!("Guess value must be between 1 and 100, got {}.", value);
}
@@ -91,13 +89,13 @@ impl Guess {
}
}
- pub fn value(&self) -> u32 {
+ pub fn value(&self) -> i32 {
self.value
}
}
```
-示例 9-9:一个 `Guess` 类型,它只在值位于 1 和 100 之间时才继续
+示例 9-10:一个 `Guess` 类型,它只在值位于 1 和 100 之间时才继续
首先,我们定义了一个包含 `u32` 类型字段 `value` 的结构体 `Guess`。这里是储存猜测值的地方。
@@ -109,6 +107,6 @@ impl Guess {
## 总结
-Rust 的错误处理功能被设计为帮助你编写更加健壮的代码。`panic!` 宏代表一个程序无法处理的状态,并停止执行而不是使用无效或不正确的值继续处理。Rust 类型系统的 `Result` 枚举代表操作可能会在一种可以恢复的情况下失败。可以使用 `Result` 来告诉代码调用者他需要处理潜在的成功或失败。在适当的场景使用 `panic!` 和 `Result` 将会使你的代码在面对无处不在的错误时显得更加可靠。
+Rust 的错误处理功能被设计为帮助你编写更加健壮的代码。`panic!` 宏代表一个程序无法处理的状态,并停止执行而不是使用无效或不正确的值继续处理。Rust 类型系统的 `Result` 枚举代表操作可能会在一种可以恢复的情况下失败。可以使用 `Result` 来告诉代码调用者他需要处理潜在的成功或失败。在适当的场景使用 `panic!` 和 `Result` 将会使你的代码在面对不可避免的错误时显得更加可靠。
现在我们已经见识过了标准库中 `Option` 和 `Result` 泛型枚举的能力了,在下一章让我们聊聊泛型是如何工作的,以及如何在你的代码中使用他们。
diff --git a/src/ch10-00-generics.md b/src/ch10-00-generics.md
index 21f81cf..d1f24f0 100644
--- a/src/ch10-00-generics.md
+++ b/src/ch10-00-generics.md
@@ -1,10 +1,10 @@
# 泛型、trait 和生命周期
-> [ch10-00-generics.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch10-00-generics.md)
+> [ch10-00-generics.md](https://github.com/rust-lang/book/blob/master/src/ch10-00-generics.md)
>
-> commit 9e8d1ed7a1732d9cc09606a9b62ee8838998502f
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
-每一个编程语言都有高效的处理重复概念的工具;在 Rust 中其工具之一就是 **泛型**(*generics*)。泛型是具体类型或其他属性的抽象替代。我们可以表达泛型的属性,比如他们的行为或如何与其他泛型相关联,而不需要在编写和编译代码时知道他们在这里实际上代表什么。
+每一个编程语言都有高效的处理重复概念的工具.在 Rust 中其工具之一就是 **泛型**(*generics*)。泛型是具体类型或其他属性的抽象替代。我们可以表达泛型的属性,比如他们的行为或如何与其他泛型相关联,而不需要在编写和编译代码时知道他们在这里实际上代表什么。
同理为了编写一份可以用于多种具体值的代码,函数并不知道其参数为何值,这时就可以让函数获取泛型而不是像 `i32` 或 `String` 这样的具体值。我们已经使用过第六章的 `Option`,第八章的 `Vec` 和 `HashMap`,以及第九章的 `Result` 这些泛型了。本章会探索如何使用泛型定义我们自己的类型、函数和方法!
@@ -45,7 +45,7 @@ fn main() {
如果需要在两个不同的列表中寻找最大值,我们可以重复示例 10-1 中的代码,这样程序中就会存在两段相同逻辑的代码,如示例 10-2 所示:
-Filename: src/main.rs
+文件名: src/main.rs
```rust
fn main() {
@@ -79,10 +79,6 @@ fn main() {
虽然代码能够执行,但是重复的代码是冗余且容易出错的,并且意味着当更新逻辑时需要修改多处地方的代码。
-
-
-
为了消除重复,我们可以创建一层抽象,在这个例子中将表现为一个获取任意整型列表作为参数并对其进行处理的函数。这将增加代码的简洁性并让我们将表达和推导寻找列表中最大值的这个概念与使用这个概念的特定位置相互独立。
在示例 10-3 的程序中将寻找最大值的代码提取到了一个叫做 `largest` 的函数中。这个程序可以找出两个不同数字列表的最大值,不过示例 10-1 中的代码只存在于一个位置:
@@ -123,9 +119,9 @@ fn main() {
从示例 10-2 到示例 10-3 中涉及的机制经历了如下几步:
-1. 我们注意到了重复代码。
-2. 我们将重复代码提取到了一个函数中,并在函数签名中指定了代码中的输入和返回值。
-3. 我们将重复代码的两个实例,改为调用函数。
+1. 找出重复代码。
+2. 将重复代码提取到了一个函数中,并在函数签名中指定了代码中的输入和返回值。
+3. 将重复代码的两个实例,改为调用函数。
在不同的场景使用不同的方式,我们也可以利用相同的步骤和泛型来减少重复代码。与函数体可以在抽象`list`而不是特定值上操作的方式相同,泛型允许代码对抽象类型进行操作。
diff --git a/src/ch10-01-syntax.md b/src/ch10-01-syntax.md
index d4f1cf4..63b8df5 100644
--- a/src/ch10-01-syntax.md
+++ b/src/ch10-01-syntax.md
@@ -1,17 +1,16 @@
## 泛型数据类型
-> [ch10-01-syntax.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch10-01-syntax.md)
+> [ch10-01-syntax.md](https://github.com/rust-lang/book/blob/master/src/ch10-01-syntax.md)
>
-> commit 9e8d1ed7a1732d9cc09606a9b62ee8838998502f
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
-泛型用于为函数签名或结构体等创建定义,允许我们创建许多不同的具体数据类型。让我们看看如何使用泛型定义函数、结构体、枚举和方法,然后我们将讨论泛型如何影响代码性能。
+我们可以使用泛型为像函数签名或结构体这样的项创建定义,这样它们就可以用于多种不同的具体数据类型。让我们看看如何使用泛型定义函数、结构体、枚举和方法,然后我们将讨论泛型如何影响代码性能。
### 在函数定义中使用泛型
-定义函数时可以在函数签名的参数数据类型和返回值中使用泛型。以这种方式编写的代码将更灵活并能向函数调用者提供更多功能,同时不引入重复代码。
-
-回到 `largest` 函数上,示例 10-4 中展示了两个提供了相同的寻找 slice 中最大值功能的函数。第一个是从示例 10-3 中提取的寻找 slice 中 `i32` 最大值的函数。第二个函数寻找 slice 中 `char` 的最大值:
+当使用泛型定义函数时,我们在函数签名中通常为参数和返回值指定数据类型的位置放置泛型。以这种方式编写的代码将更灵活并能向函数调用者提供更多功能,同时不引入重复代码。
+回到 `largest` 函数上,示例 10-4 中展示了两个提供了相同的寻找 slice 中最大值功能的函数。
文件名: src/main.rs
@@ -57,13 +56,11 @@ fn main() {
示例 10-4:两个只在名称和签名中类型有所不同的函数
-这里 `largest_i32` 和 `largest_char` 有着完全相同的函数体,所以如果能够将这两个函数变成一个来减少重复就太好了。所幸通过引入一个泛型参数就能实现!
-
-为了参数化要定义的函数的签名中的类型,我们需要像给函数的值参数起名那样为这类型参数起一个名字。这里选择了名称 `T`。任何标识符都可以作为类型参数名,选择 `T` 是因为 Rust 的类型命名规范是骆驼命名法(CamelCase)。另外泛型类型参数的规范也倾向于简短,经常仅仅是一个字母。`T` 作为 “type” 的缩写是大部分 Rust 程序员的首选。
+`largest_i32` 函数是从示例 10-3 中提取的寻找 slice 中 `i32` 最大值的函数。`largest_char` 函数寻找 slice 中 `char` 的最大值:这两个函数有着相同的代码,所以让我们在一个单独的函数中引入泛型参数来消除重复。
-当需要在函数体中使用一个参数时,必须在函数签名中声明这个参数以便编译器能知道函数体中这个名称的意义。同理,当在函数签名中使用一个类型参数时,必须在使用它之前就声明它。类型参数声明位于函数名称与参数列表中间的尖括号中。
+为了参数化要定义的函数的签名中的类型,我们需要像给函数的值参数起名那样为这类型参数起一个名字。任何标识符都可以作为类型参数名。不过选择 `T` 是因为 Rust 的习惯是让变量名尽量短,通常就只有一个字母,同时 Rust 类型命名规范是骆驼命名法(CamelCase)。`T` 作为 “type” 的缩写是大部分 Rust 程序员的首选。
-我们将要定义的泛型版本的 `largest` 函数的签名看起来像这样:
+当需要在函数体中使用一个参数时,必须在函数签名中声明这个参数以便编译器能知道函数体中这个名称的意义。同理,当在函数签名中使用一个类型参数时,必须在使用它之前就声明它。为了定义泛型版本的 `largest` 函数,类型参数声明位于函数名称与参数列表中间的尖括号 `<>` 中,像这样:
```rust,ignore
fn largest(list: &[T]) -> T {
@@ -71,11 +68,11 @@ fn largest(list: &[T]) -> T {
这可以理解为:函数 `largest` 有泛型类型 `T`。它有一个参数 `list`,它的类型是一个 `T` 值的 slice。`largest` 函数将会返回一个与 `T` 相同类型的值。
-示例 10-5 展示一个在签名中使用了泛型的统一的 `largest` 函数定义,并向我们展示了如何对 `i32` 值的 slice 或 `char` 值的 slice 调用 `largest` 函数。注意这些代码还不能编译!
+示例 10-5 展示一个在签名中使用了泛型的统一的 `largest` 函数定义。该示例也向我们展示了如何对 `i32` 值的 slice 或 `char` 值的 slice 调用 `largest` 函数。注意这些代码还不能编译,不过本章稍后部分会修复错误。
文件名: src/main.rs
-```rust,ignore
+```rust,ignore,does_not_compile
fn largest(list: &[T]) -> T {
let mut largest = list[0];
@@ -107,22 +104,17 @@ fn main() {
```text
error[E0369]: binary operation `>` cannot be applied to type `T`
+ --> src/main.rs:5:12
|
5 | if item > largest {
- | ^^^^
+ | ^^^^^^^^^^^^^^
|
-note: an implementation of `std::cmp::PartialOrd` might be missing for `T`
+ = note: an implementation of `std::cmp::PartialOrd` might be missing for `T`
```
-注释中提到了 `std::cmp::PartialOrd`,这是一个 *trait*。下一部分会讲到 trait,不过简单来说,这个错误表明 `largest` 的函数体不能适用于 `T` 的所有可能的类型;因为在函数体需要比较 `T` 类型的值,不过它只能用于我们知道如何排序的类型。标准库中定义的 `std::cmp::PartialOrd` trait 可以实现类型的比较功能。在下一部分会再次回到 trait 并讲解如何为泛型指定一个 trait,不过让我们先把这个例子放在一边并探索其他那些可以使用泛型类型参数的地方。
+注释中提到了 `std::cmp::PartialOrd`,这是一个 *trait*。下一部分会讲到 trait。不过简单来说,这个错误表明 `largest` 的函数体不能适用于 `T` 的所有可能的类型。因为在函数体需要比较 `T` 类型的值,不过它只能用于我们知道如何排序的类型。为了开启比较功能,标准库中定义的 `std::cmp::PartialOrd` trait 可以实现类型的比较功能(查看附录 C 获取该 trait 的更多信息)。
-
+标准库中定义的 `std::cmp::PartialOrd` trait 可以实现类型的比较功能。在 “trait bound” 部分会讲解如何指定泛型实现特定的 trait,不过让我们先探索其他使用泛型参数的方法。
### 结构体定义中的泛型
@@ -146,11 +138,11 @@ fn main() {
其语法类似于函数定义中使用泛型。首先,必须在结构体名称后面的尖括号中声明泛型参数的名称。接着在结构体定义中可以指定具体数据类型的位置使用泛型类型。
-注意 `Point` 的定义中只使用了一个泛型类型,我们想要表达的是结构体 `Point` 对于一些类型 `T` 是泛型的,而且字段 `x` 和 `y` **都是** 相同类型的,无论它具体是何类型。如果尝试创建一个有不同类型值的 `Point` 的实例,像示例 10-7 中的代码就不能编译:
+注意 `Point` 的定义中只使用了一个泛型类型,这个定义表明结构体 `Point` 对于一些类型 `T` 是泛型的,而且字段 `x` 和 `y` **都是** 相同类型的,无论它具体是何类型。如果尝试创建一个有不同类型值的 `Point` 的实例,像示例 10-7 中的代码就不能编译:
文件名: src/main.rs
-```rust,ignore
+```rust,ignore,does_not_compile
struct Point {
x: T,
y: T,
@@ -163,22 +155,20 @@ fn main() {
示例 10-7:字段 `x` 和 `y` 必须是相同类型,因为他们都有相同的泛型类型 `T`
-尝试编译会得到如下错误:
+在这个例子中,当把整型值 5 赋值给 `x` 时,就告诉了编译器这个 `Point` 实例中的泛型 `T` 是整型的。接着指定 `y` 为 4.0,它被定义为与 `x` 相同类型,就会得到一个像这样的类型不匹配错误:
```text
error[E0308]: mismatched types
- -->
+ --> src/main.rs:7:38
|
7 | let wont_work = Point { x: 5, y: 4.0 };
| ^^^ expected integral variable, found
- floating-point variable
+floating-point variable
|
= note: expected type `{integer}`
- = note: found type `{float}`
+ found type `{float}`
```
-当我们将 5 赋值给 `x`,编译器就知道这个 `Point` 实例的泛型类型 `T` 是一个整型。接着我们将 `y` 指定为 4.0,而它被定义为与 `x` 有着相同的类型,所以出现了类型不匹配的错误。
-
如果想要定义一个 `x` 和 `y` 可以有不同类型且仍然是泛型的 `Point` 结构体,我们可以使用多个泛型类型参数。在示例 10-8 中,我们修改 `Point` 的定义为拥有两个泛型类型 `T` 和 `U`。其中字段 `x` 是 `T` 类型的,而字段 `y` 是 `U` 类型的:
文件名: src/main.rs
@@ -200,9 +190,9 @@ fn main() {
现在所有这些 `Point` 实例都是被允许的了!你可以在定义中使用任意多的泛型类型参数,不过太多的话代码将难以阅读和理解。当你的代码中需要许多泛型类型时,它可能表明你的代码需要重组为更小的部分。
-### 枚举定义中的泛型数据类型
+### 枚举定义中的泛型
-类似于结构体,枚举也可以在其成员中存放泛型数据类型。第六章我们使用过了标准库提供的 `Option` 枚举,现在这个定义看起来就更容易理解了。让我们再看看:
+类似于结构体,枚举也可以在其成员中存放泛型数据类型。第六章我们使用过了标准库提供的 `Option` 枚举,让我们再看看:
```rust
enum Option {
@@ -211,7 +201,7 @@ enum Option {
}
```
-换句话说 `Option` 是一个拥有泛型 `T` 的枚举。它有两个成员:`Some`,它存放了一个类型 `T` 的值,和不存在任何值的`None`。标准库中只有这一个定义来支持创建任何具体类型的枚举值。“一个可能的值” 是一个比具体类型的值更抽象的概念,而 Rust 允许我们不引入重复代码就能表现抽象的概念。
+现在这个定义看起来就更容易理解了。如你所见 `Option` 是一个拥有泛型 `T` 的枚举,它有两个成员:`Some`,它存放了一个类型 `T` 的值,和不存在任何值的`None`。通过 `Option` 枚举可以表达有一个可能的值的抽象概念,同时因为 `Option` 是泛型的,无论这个可能的值是什么类型都可以使用这个抽象。
枚举也可以拥有多个泛型类型。第九章使用过的 `Result` 枚举定义就是一个这样的例子:
@@ -226,9 +216,11 @@ enum Result {
当发现代码中有多个只有存放的值的类型有所不同的结构体或枚举定义时,你就应该像之前的函数定义中那样引入泛型类型来减少重复代码。
-### 方法定义中的枚举数据类型
+### 方法定义中的泛型
+
+也可以在定义中使用泛型在结构体和枚举上实现方法(像第五章那样)。
-可以像第五章介绍的那样来为其定义中带有泛型的结构体或枚举实现方法。示例 10-9 中展示了示例 10-6 中定义的结构体 `Point`。接着我们在 `Point` 上定义了一个叫做 `x` 的方法来返回字段 `x` 中数据的引用:
+可以像第五章介绍的那样来为其定义中带有泛型的结构体或枚举实现方法。示例 10-9 中展示了示例 10-6 中定义的结构体 `Point`,和在其上实现的名为 `x` 的方法。
文件名: src/main.rs
@@ -253,7 +245,11 @@ fn main() {
示例 10-9:在 `Point` 结构体上实现方法 `x`,它返回 `T` 类型的字段 `x` 的引用
-注意必须在 `impl` 后面声明 `T`,这样就可以在 `Point` 上实现的方法中使用它了。在 `impl` 之后声明泛型 `T` ,这样 Rust 就知道 `Point` 的尖括号中的类型是泛型而不是具体类型。例如,可以选择为 `Point` 实例实现方法,而不是为泛型 `Point` 实例。示例 10-10 展示了一个没有在 `impl` 之后(的尖括号)声明泛型的例子,这里使用了一个具体类型,`f32`:
+这里在 `Point` 上定义了一个叫做 `x` 的方法来返回字段 `x` 中数据的引用:
+
+注意必须在 `impl` 后面声明 `T`,这样就可以在 `Point` 上实现的方法中使用它了。在 `impl` 之后声明泛型 `T` ,这样 Rust 就知道 `Point` 的尖括号中的类型是泛型而不是具体类型。
+
+例如,可以选择为 `Point` 实例实现方法,而不是为泛型 `Point` 实例。示例 10-10 展示了一个没有在 `impl` 之后(的尖括号)声明泛型的例子,这里使用了一个具体类型,`f32`:
```rust
# struct Point {
@@ -270,7 +266,7 @@ impl Point {
示例 10-10:构建一个只用于拥有泛型参数 `T` 的结构体的具体类型的 `impl` 块
-这段代码意味着 `Point` 类型会有一个方法 `distance_from_origin`,而其他 `T` 不是 `f32` 类型的 `Point` 实例则没有定义此方法。这个方法计算点实例与另一个点坐标之间的距离,它使用了只能用于浮点型的数学运算符。
+这段代码意味着 `Point` 类型会有一个方法 `distance_from_origin`,而其他 `T` 不是 `f32` 类型的 `Point` 实例则没有定义此方法。这个方法计算点实例与坐标 (0.0, 0.0) 之间的距离,并使用了只能用于浮点型的数学运算符。
结构体定义中的泛型类型参数并不总是与结构体方法签名中使用的泛型是同一类型。示例 10-11 中在示例 10-8 中的结构体 `Point` 上定义了一个方法 `mixup`。这个方法获取另一个 `Point` 作为参数,而它可能与调用 `mixup` 的 `self` 是不同的 `Point` 类型。这个方法用 `self` 的 `Point` 类型的 `x` 值(类型 `T`)和参数的 `Point` 类型的 `y` 值(类型 `W`)来创建一个新 `Point` 类型的实例:
@@ -305,11 +301,11 @@ fn main() {
在 `main` 函数中,定义了一个有 `i32` 类型的 `x`(其值为 `5`)和 `f64` 的 `y`(其值为 `10.4`)的 `Point`。`p2` 则是一个有着字符串 slice 类型的 `x`(其值为 `"Hello"`)和 `char` 类型的 `y`(其值为`c`)的 `Point`。在 `p1` 上以 `p2` 作为参数调用 `mixup` 会返回一个 `p3`,它会有一个 `i32` 类型的 `x`,因为 `x` 来自 `p1`,并拥有一个 `char` 类型的 `y`,因为 `y` 来自 `p2`。`println!` 会打印出 `p3.x = 5, p3.y = c`。
-注意泛型参数 `T` 和 `U` 声明于 `impl` 之后,因为他们与结构体定义相对应。而泛型参数 `V` 和 `W` 声明于 `fn mixup` 之后,因为他们只是相对于方法本身的。
+这个例子的目的是展示一些泛型通过 `impl` 声明而另一些通过方法定义声明的情况。这里泛型参数 `T` 和 `U` 声明于 `impl` 之后,因为他们与结构体定义相对应。而泛型参数 `V` 和 `W` 声明于 `fn mixup` 之后,因为他们只是相对于方法本身的。
### 泛型代码的性能
-在阅读本部分内容的同时,你可能会好奇使用泛型类型参数是否会有运行时消耗。好消息是:Rust 实现了泛型,使得使用泛型类型参数的代码相比使用具体类型并没有任何速度上的损失!
+在阅读本部分内容的同时,你可能会好奇使用泛型类型参数是否会有运行时消耗。好消息是:Rust 实现了泛型,使得使用泛型类型参数的代码相比使用具体类型并没有任何速度上的损失。
Rust 通过在编译时进行泛型代码的 **单态化**(*monomorphization*)来保证效率。单态化是一个通过填充编译时使用的具体类型,将通用代码转换为特定代码的过程。
diff --git a/src/ch10-02-traits.md b/src/ch10-02-traits.md
index 37e47d0..bd51a3e 100644
--- a/src/ch10-02-traits.md
+++ b/src/ch10-02-traits.md
@@ -1,10 +1,10 @@
## trait:定义共享的行为
-> [ch10-02-traits.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch10-02-traits.md)
+> [ch10-02-traits.md](https://github.com/rust-lang/book/blob/master/src/ch10-02-traits.md)
>
-> commit 131859023a0a6be67168d36dcdc8e2aa43f806fd
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
-trait 允许我们进行另一种抽象:他们让我们可以抽象类型所通用的行为。*trait* 告诉 Rust 编译器某个特定类型拥有可能与其他类型共享的功能。在使用泛型类型参数的场景中,可以使用 *trait bounds* 在编译时指定泛型可以是任何实现了某个 trait 的类型,并由此在这个场景下拥有我们希望的功能。
+*trait* 告诉 Rust 编译器某个特定类型拥有可能与其他类型共享的功能。可以通过 trait 以一种抽象的方式定义共享的行为。可以使用 *trait bounds* 指定泛型是任何拥有特定行为的类型。
> 注意:*trait* 类似于其他语言中的常被称为 **接口**(*interfaces*)的功能,虽然有一些不同。
@@ -12,33 +12,35 @@ trait 允许我们进行另一种抽象:他们让我们可以抽象类型所
一个类型的行为由其可供调用的方法构成。如果可以对不同类型调用相同的方法的话,这些类型就可以共享相同的行为了。trait 定义是一种将方法签名组合起来的方法,目的是定义一个实现某些目的所必需的行为的集合。
-例如,这里有多个存放了不同类型和属性文本的结构体:结构体 `NewsArticle` 用于存放发生于世界各地的新闻故事,而结构体 `Tweet` 最多只能存放 140 个字符的内容,以及像是否转推或是否是对推友的回复这样的元数据。
+例如,这里有多个存放了不同类型和属性文本的结构体:结构体 `NewsArticle` 用于存放发生于世界各地的新闻故事,而结构体 `Tweet` 最多只能存放 280 个字符的内容,以及像是否转推或是否是对推友的回复这样的元数据。
-我们想要创建一个多媒体聚合库用来显示可能储存在 `NewsArticle` 或 `Tweet` 实例中的数据的总结。每一个结构体都需要的行为是他们是能够被总结的,这样的话就可以调用实例的 `summary` 方法来请求总结。示例 10-12 中展示了一个表现这个概念的 `Summarizable` trait 的定义:
+我们想要创建一个多媒体聚合库用来显示可能储存在 `NewsArticle` 或 `Tweet` 实例中的数据的总结。每一个结构体都需要的行为是他们是能够被总结的,这样的话就可以调用实例的 `summarize` 方法来请求总结。示例 10-12 中展示了一个表现这个概念的 `Summary` trait 的定义:
-文件名: lib.rs
+文件名: src/lib.rs
```rust
-pub trait Summarizable {
- fn summary(&self) -> String;
+pub trait Summary {
+ fn summarize(&self) -> String;
}
```
-示例 10-12:`Summarizable` trait 定义,它包含由 `summary` 方法提供的行为
+示例 10-12:`Summarizable` trait 定义,它包含由 `summarize` 方法提供的行为
-使用 `trait` 关键字来声明一个 trait,后面是 trait 的名字,在这个例子中是 `Summarizable`。在大括号中声明描述实现这个 trait 的类型所需要的行为的方法签名,在这个例子中是 `fn summary(&self) -> String`。在方法签名后跟分号,而不是在大括号中提供其实现。接着每一个实现这个 trait 的类型都需要提供其自定义行为的方法体,编译器也会确保任何实现 `Summarizable` trait 的类型都拥有与这个签名的定义完全一致的 `summary` 方法。
+这里使用 `trait` 关键字来声明一个 trait,后面是 trait 的名字,在这个例子中是 `Summary`。在大括号中声明描述实现这个 trait 的类型所需要的行为的方法签名,在这个例子中是 `fn summarize(&self) -> String`。
-trait 体中可以有多个方法,一行一个方法签名且都以分号结尾。
+在方法签名后跟分号,而不是在大括号中提供其实现。接着每一个实现这个 trait 的类型都需要提供其自定义行为的方法体,编译器也会确保任何实现 `Summary` trait 的类型都拥有与这个签名的定义完全一致的 `summarize` 方法。
+
+trait 体中可以有多个方法:一行一个方法签名且都以分号结尾。
### 为类型实现 trait
-现在我们定义了 `Summarizable` trait,接着就可以在多媒体聚合库中需要拥有这个行为的类型上实现它了。示例 10-13 中展示了 `NewsArticle` 结构体上 `Summarizable` trait 的一个实现,它使用标题、作者和创建的位置作为 `summary` 的返回值。对于 `Tweet` 结构体,我们选择将 `summary` 定义为用户名后跟推文的全部文本作为返回值,并假设推文内容已经被限制为 140 字符以内。
+现在我们定义了 `Summary` trait,接着就可以在多媒体聚合库中需要拥有这个行为的类型上实现它了。示例 10-13 中展示了 `NewsArticle` 结构体上 `Summary` trait 的一个实现,它使用标题、作者和创建的位置作为 `summarize` 的返回值。对于 `Tweet` 结构体,我们选择将 `summarize` 定义为用户名后跟推文的全部文本作为返回值,并假设推文内容已经被限制为 280 字符以内。
-文件名: lib.rs
+文件名: src/lib.rs
```rust
-# pub trait Summarizable {
-# fn summary(&self) -> String;
+# pub trait Summary {
+# fn summarize(&self) -> String;
# }
#
pub struct NewsArticle {
@@ -48,8 +50,8 @@ pub struct NewsArticle {
pub content: String,
}
-impl Summarizable for NewsArticle {
- fn summary(&self) -> String {
+impl Summary for NewsArticle {
+ fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
@@ -61,14 +63,14 @@ pub struct Tweet {
pub retweet: bool,
}
-impl Summarizable for Tweet {
- fn summary(&self) -> String {
+impl Summary for Tweet {
+ fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
```
-示例 10-13:在 `NewsArticle` 和 `Tweet` 类型上实现 `Summarizable` trait
+示例 10-13:在 `NewsArticle` 和 `Tweet` 类型上实现 `Summary` trait
在类型上实现 trait 类似于实现与 trait 无关的方法。区别在于 `impl` 关键字之后,我们提供需要实现 trait 的名称,接着是 `for` 和需要实现 trait 的类型的名称。在 `impl` 块中,使用 trait 定义中的方法签名,不过不再后跟分号,而是需要在大括号中编写函数体来为特定类型实现 trait 方法所拥有的行为。
@@ -82,66 +84,38 @@ let tweet = Tweet {
retweet: false,
};
-println!("1 new tweet: {}", tweet.summary());
+println!("1 new tweet: {}", tweet.summarize());
```
这会打印出 `1 new tweet: horse_ebooks: of course, as you probably already know, people`。
-注意因为示例 10-13 中我们在相同的 `lib.rs` 里定义了 `Summarizable` trait 和 `NewsArticle` 与 `Tweet` 类型,所以他们是位于同一作用域的。如果这个 `lib.rs` 是对应 `aggregator` crate 的,而别人想要利用我们 crate 的功能外加为其 `WeatherForecast` 结构体实现 `Summarizable` trait,在实现 `Summarizable` trait 之前他们首先就需要将其导入其作用域中,如示例 10-14 所示:
-
-文件名: lib.rs
-
-```rust,ignore
-extern crate aggregator;
+注意因为示例 10-13 中我们在相同的 *lib.rs* 里定义了 `Summary` trait 和 `NewsArticle` 与 `Tweet` 类型,所以他们是位于同一作用域的。如果这个 *lib.rs* 是对应 `aggregator` crate 的,而别人想要利用我们 crate 的功能为其自己的库作用域中的结构体实现 `Summary` trait。首先他们需要将 trait 引入作用域。这可以通过指定 `use aggregator::Summary;` 实现,这样就可以为其类型实现 `Summary` trait 了。`Summary` 还必须是公有 trait 使得其他 crate 可以实现它,这也是为什么实例 10-12 中将 `pub` 置于 `trait` 之前。
-use aggregator::Summarizable;
+实现 trait 时需要注意的是,只有当 trait 或者要实现 trait 的类型位于 crate 的本地作用域时,才能为该类型实现 trait。例如,可以为多媒体聚合库 crate 的自定义类型 `Tweet` 实现如标准库中的 `Display` trait,这是因为 `Tweet` 类型位于多媒体聚合库 crate 本地的作用域中。类似地,也可以在多媒体聚合库 crate 中为 `Vec` 实现 `Summary`,这是因为 `Summary` trait 位于多媒体聚合库 crate 本地作用域中。
-struct WeatherForecast {
- high_temp: f64,
- low_temp: f64,
- chance_of_precipitation: f64,
-}
-
-impl Summarizable for WeatherForecast {
- fn summary(&self) -> String {
- format!("The high will be {}, and the low will be {}. The chance of
- precipitation is {}%.", self.high_temp, self.low_temp,
- self.chance_of_precipitation)
- }
-}
-```
-
-示例 10-14:在另一个 crate 中将 `aggregator` crate 的 `Summarizable` trait 引入作用域
-
-另外这段代码假设 `Summarizable` 是一个公有 trait,这是因为示例 10-12 中 `trait` 之前使用了 `pub` 关键字。
-
-trait 实现的一个需要注意的限制是:只能在 trait 或对应类型位于我们 crate 本地的时候为其实现 trait。换句话说,不允许对外部类型实现外部 trait。例如,不能在 `Vec` 上实现 `Display` trait,因为 `Display` 和 `Vec` 都定义于标准库中。允许在像 `Tweet` 这样作为我们 `aggregator`crate 部分功能的自定义类型上实现标准库中的 trait `Display`。也允许在 `aggregator`crate 中为 `Vec` 实现 `Summarizable`,因为 `Summarizable` 定义于此。这个限制是我们称为 **孤儿规则**(*orphan rule*)的一部分,如果你感兴趣的可以在类型理论中找到它。简单来说,它被称为 orphan rule 是因为其父类型不存在。没有这条规则的话,两个 crate 可以分别对相同类型实现相同的 trait,因而这两个实现会相互冲突:Rust 将无从得知应该使用哪一个。因为 Rust 强制执行 orphan rule,其他人编写的代码不会破坏你代码,反之亦是如此。
+但是不能为外部类型实现外部 trait。例如,不能在多媒体聚合库 crate 中为 `Vec` 实现 `Display` trait。这是因为 `Display` 和 `Vec` 都定义于标准库中,它们并不位于多媒体聚合库的 crate 本地作用域中。这个限制是被称为 **相干性**(*coherence*) 的程序属性的一部分,或者更具体的说是 **孤儿规则**(*orphan rule*),其得名于不存在父类型。这条规则确保了其他人编写的代码不会破坏你代码,反之亦然。没有这条规则的话,两个 crate 可以分别对相同类型实现相同的 trait,而 Rust 将无从得知应该使用哪一个实现。
### 默认实现
有时为 trait 中的某些或全部方法提供默认的行为,而不是在每个类型的每个实现中都定义自己的行为是很有用的。这样当为某个特定类型实现 trait 时,可以选择保留或重载每个方法的默认行为。
-示例 10-15 中展示了如何为 `Summarize` trait 的 `summary` 方法指定一个默认的字符串值,而不是像示例 10-12 中那样只是定义方法签名:
+示例 10-14 中展示了如何为 `Summary` trait 的 `summarize` 方法指定一个默认的字符串值,而不是像示例 10-12 中那样只是定义方法签名:
-文件名: lib.rs
+文件名: src/lib.rs
```rust
-pub trait Summarizable {
- fn summary(&self) -> String {
+pub trait Summary {
+ fn summarize(&self) -> String {
String::from("(Read more...)")
}
}
```
-示例 10-15:`Summarizable` trait 的定义,带有一个 `summary` 方法的默认实现
+示例 10-14:`Summary` trait 的定义,带有一个 `summarize` 方法的默认实现
-如果想要对 `NewsArticle` 实例使用这个默认实现,而不是像示例 10-13 中那样定义一个自己的实现,则可以指定一个空的 `impl` 块:
-
-```rust,ignore
-impl Summarizable for NewsArticle {}
-```
+如果想要对 `NewsArticle` 实例使用这个默认实现,而不是定义一个自己的实现,则可以通过 impl Summary for NewsArticle {} 指定一个空的 `impl` 块。
-即便选择不再直接为 `NewsArticle` 定义 `summary` 方法了,因为 `summary` 方法有一个默认实现而且 `NewsArticle` 被指定为实现了 `Summarizable` trait,我们仍然可以对 `NewsArticle` 的实例调用 `summary` 方法:
+即便选择不再直接为 `NewsArticle` 定义 `summarize` 方法了,因为我们提供了一个默认实现而且 `NewsArticle` 被指定为实现了 `Summary` trait。为此我们仍然可以像这样对 `NewsArticle` 的实例调用 `summarize` 方法:
```rust,ignore
let article = NewsArticle {
@@ -152,36 +126,38 @@ let article = NewsArticle {
hockey team in the NHL."),
};
-println!("New article available! {}", article.summary());
+println!("New article available! {}", article.summarize());
```
这段代码会打印 `New article available! (Read more...)`。
+为 `summarize` 创建默认实现并不要求对示例 10-13 中 `Tweet` 上的 `Summary` 实现做任何改变。其原因是重载一个默认实现的语法与实现没有默认实现的 trait 方法一样。
+
将 `Summarizable` trait 改变为拥有默认 `summary` 实现并不要求对示例 10-13 中 `Tweet` 和示例 10-14 中 `WeatherForecast` 的 `Summarizable` 实现做任何改变:重载一个默认实现的语法与实现没有默认实现的 trait 方法时完全一样的。
-默认实现允许调用相同 trait 中的其他方法,哪怕这些方法没有默认实现。通过这种方法,trait 可以实现很多有用的功能而只需实现一小部分特定内容。我们可以选择让`Summarizable` trait 也拥有一个要求实现的`author_summary` 方法,接着 `summary` 方法则提供默认实现并调用 `author_summary` 方法:
+默认实现允许调用相同 trait 中的其他方法,哪怕这些方法没有默认实现。如此,trait 可以实现很多有用的功能而只需实现一小部分特定内容。我们可以选择让`Summary` trait 也拥有一个要求实现的`summarize_author` 方法,接着 `summarize` 方法则提供默认实现并调用 `summarize_author` 方法:
```rust
-pub trait Summarizable {
- fn author_summary(&self) -> String;
+pub trait Summary {
+ fn summarize_author(&self) -> String;
- fn summary(&self) -> String {
- format!("(Read more from {}...)", self.author_summary())
+ fn summarize(&self) -> String {
+ format!("(Read more from {}...)", self.summarize_author())
}
}
```
-为了使用这个版本的 `Summarizable`,只需在实现 trait 时定义 `author_summary` 即可:
+为了使用这个版本的 `Summary`,只需在实现 trait 时定义 `summarize_author` 即可:
```rust,ignore
-impl Summarizable for Tweet {
- fn author_summary(&self) -> String {
+impl Summary for Tweet {
+ fn summarize_author(&self) -> String {
format!("@{}", self.username)
}
}
```
-一旦定义了 `author_summary`,我们就可以对 `Tweet` 结构体的实例调用 `summary` 了,而 `summary` 的默认实现会调用我们提供的 `author_summary` 定义。
+一旦定义了 `summarize_author`,我们就可以对 `Tweet` 结构体的实例调用 `summarize` 了,而 `summary` 的默认实现会调用我们提供的 `summarize_author` 定义。因为实现了 `summarize_author`,`Summary` trait 就提供了 `summarize` 方法的功能,且无需编写更多的代码。
```rust,ignore
let tweet = Tweet {
@@ -191,37 +167,74 @@ let tweet = Tweet {
retweet: false,
};
-println!("1 new tweet: {}", tweet.summary());
+println!("1 new tweet: {}", tweet.summarize());
```
这会打印出 `1 new tweet: (Read more from @horse_ebooks...)`。
-注意在重载过的实现中调用默认实现是不可能的。
+注意无法从相同方法的重载实现中调用默认方法。
+
+### trait 作为参数
+
+知道了如何定义 trait 和在类型上实现这些 trait 之后,我们可以探索一下如何使用 trait 来接受多种不同类型的参数。
+
+例如在示例 10-13 中为 `NewsArticle` 和 `Tweet` 类型实现了 `Summary` trait。我们可以定义一个函数 `notify` 来调用其参数 `item` 上的 `summarize` 方法,该参数为一些实现了 `Summary` trait 的方法。为此可以使用 ‘`impl Trait`’ 语法,像这样:
+
+```rust,ignore
+pub fn notify(item: impl Summary) {
+ println!("Breaking news! {}", item.summarize());
+}
+```
-### Trait Bounds
+在 `notify` 函数体中,可以调用任何来自 `Summary` trait 的方法,比如 `summarize`。
-现在我们定义了 trait 并在类型上实现了这些 trait,也可以对泛型类型参数使用 trait。我们可以限制泛型不再适用于任何类型,编译器会确保其被限制为那些实现了特定 trait 的类型,由此泛型就会拥有我们希望其类型所拥有的功能。这被称为指定泛型的 *trait bounds*。
+#### Trait Bounds
-例如在示例 10-13 中为 `NewsArticle` 和 `Tweet` 类型实现了 `Summarizable` trait。我们可以定义一个函数 `notify` 来调用 `summary` 方法,它拥有一个泛型类型 `T` 的参数 `item`。为了能够在 `item` 上调用 `summary` 而不出现错误,我们可以在 `T` 上使用 trait bounds 来指定 `item` 必须是实现了 `Summarizable` trait 的类型:
+`impl Trait` 语法适用于短小的例子,它不过是一个较长形式的语法糖。这被称为 *trait bound*,这看起来像:
```rust,ignore
-pub fn notify(item: T) {
- println!("Breaking news! {}", item.summary());
+pub fn notify(item: T) {
+ println!("Breaking news! {}", item.summarize());
}
```
-trait bounds 连同泛型类型参数声明一同出现,位于尖括号中的冒号后面。由于 `T` 上的 trait bounds,我们可以传递任何 `NewsArticle` 或 `Tweet` 的实例来调用 `notify` 函数。示例 10-14 中使用我们 `aggregator` crate 的外部代码也可以传递一个 `WeatherForecast` 的实例来调用 `notify` 函数,因为 `WeatherForecast` 同样也实现了 `Summarizable`。使用任何其他类型,比如 `String` 或 `i32`,来调用 `notify` 的代码将不能编译,因为这些类型没有实现 `Summarizable`。
+这与之前的例子相同,不过稍微冗长了一些。trait bound 与泛型参数声明在一起,位于尖括号中分号的后面。因为 `T` 的 trait bound,我们可以传递任何 `NewsArticle` 或 `Tweet` 的实例调用 `notify`。用任何其他类型,比如 `String` 或 `i32`,调用该函数的代码将不能编译,因为这些类型没有实现 `Summary`。
-可以通过 `+` 来为泛型指定多个 trait bounds。如果我们需要能够在函数中使用 `T` 类型的显示格式的同时也能使用 `summary` 方法,则可以使用 trait bounds `T: Summarizable + Display`。这意味着 `T` 可以是任何实现了 `Summarizable` 和 `Display` 的类型。
+何时应该使用这种形式而不是 `impl Trait` 呢?虽然 `impl Trait` 适用于短小的例子,trait bound 则适用于更复杂的场景。例如,比如需要获取两个实现了 `Summary` 的类型:
-对于拥有多个泛型类型参数的函数,每一个泛型都可以有其自己的 trait bounds。在函数名和参数列表之间的尖括号中指定很多的 trait bound 信息将是难以阅读的,所以有另外一个指定 trait bounds 的语法,它将其移动到函数签名后的 `where` 从句中。所以相比这样写:
+```rust,ignore
+pub fn notify(item1: impl Summary, item2: impl Summary) {
+```
+这适用于 `item1` 和 `item2` 允许是不同类型的情况(只要它们都实现了 `Summary`)。不过如果你希望强制它们都是相同类型呢?这只有在使用 trait bound 时才有可能:
+
+```rust,ignore
+pub fn notify(item1: T, item2: T) {
+```
+
+#### 通过 `+` 指定多个 trait
+
+如果 `notify` 需要显示 `item` 的格式化形式,同时也要使用 `summarize` 方法,那么 `item` 就需要同时实现两个不同的 trait:`Display` 和 `Summary`。这可以通过 `+` 语法实现:
+
+```rust,ignore
+pub fn notify(item: impl Summary + Display) {
+```
+
+这个语法也适用于泛型的 trait bound:
+
+```rust,ignore
+pub fn notify(item: T) {
+```
+
+#### 通过 `where` 简化代码
+
+然而,使用过多的 trait bound 也有缺点。每个泛型有其自己的 trait bound,所以有多个泛型参数的函数在名称和参数列表之间会有很长的 trait bound 信息,这使得函数签名难以阅读。为此,Rust 有另一个在函数签名之后的 `where` 从句中指定 trait bound 的语法。所以除了这么写:
```rust,ignore
fn some_function(t: T, u: U) -> i32 {
```
-我们也可以使用 `where` 从句:
+还可以像这样使用 `where` 从句:
```rust,ignore
fn some_function(t: T, u: U) -> i32
@@ -230,51 +243,97 @@ fn some_function(t: T, u: U) -> i32
{
```
-这就显得不那么杂乱,同时也使这个函数看起来更像没有很多 trait bounds 的函数。这时函数名、参数列表和返回值类型都离得很近。
+这个函数签名就显得不那么杂乱,函数名、参数列表和返回值类型都离得很近,看起来类似没有很多 trait bounds 的函数。
+
+### 返回 trait
+
+也可以在返回值中使用 `impl Trait` 语法,来返回实现了某个 trait 的类型:
+
+```rust,ignore
+fn returns_summarizable() -> impl Summary {
+ Tweet {
+ username: String::from("horse_ebooks"),
+ content: String::from("of course, as you probably already know, people"),
+ reply: false,
+ retweet: false,
+ }
+}
+```
+
+这个签名表明,“我要返回某个实现了 `Summary` trait 的类型,但是不确定其具体的类型”。在例子中返回了一个 `Tweet`,不过调用方并不知情。
+
+这有什么用呢?在第十三章中,我们会学些两个大量依赖 trait 的功能:闭包和迭代器。这些功能创建只有编译器知道的类型,或者是非常非常长的类型。`impl Trait` 允许你简单的说 “返回一个 `Iterator`” 而无需写出实际的冗长的类型。
+
+不过这只适用于返回单一类型的情况。例如,这样就 **不行**:
+
+```rust,ignore,does_not_compile
+fn returns_summarizable(switch: bool) -> impl Summary {
+ if switch {
+ NewsArticle {
+ headline: String::from("Penguins win the Stanley Cup Championship!"),
+ location: String::from("Pittsburgh, PA, USA"),
+ author: String::from("Iceburgh"),
+ content: String::from("The Pittsburgh Penguins once again are the best
+ hockey team in the NHL."),
+ }
+ } else {
+ Tweet {
+ username: String::from("horse_ebooks"),
+ content: String::from("of course, as you probably already know, people"),
+ reply: false,
+ retweet: false,
+ }
+ }
+}
+```
+
+这里尝试返回 `NewsArticle` 或 `Tweet`。这不能编译,因为 `impl Trait` 工作方式的限制。为了编写这样的代码,你不得不等到第十七章的 “为使用不同类型的值而设计的 trait 对象” 部分。
### 使用 trait bounds 来修复 `largest` 函数
-所以任何想要对泛型使用 trait 定义的行为的时候,都需要在泛型参数类型上指定 trait bounds。现在我们就可以修复示例 10-5 中那个使用泛型类型参数的 `largest` 函数定义了!当我们将其放置不管的时候,它会出现这个错误:
+现在你知道了如何使用泛型参数 trait bound 来指定所需的行为。让我们回到实例 10-5 修复使用泛型类型参数的 `largest` 函数定义!回顾一下,最后尝试编译代码时出现的错误是:
```text
error[E0369]: binary operation `>` cannot be applied to type `T`
+ --> src/main.rs:5:12
|
5 | if item > largest {
- | ^^^^
+ | ^^^^^^^^^^^^^^
|
-note: an implementation of `std::cmp::PartialOrd` might be missing for `T`
+ = note: an implementation of `std::cmp::PartialOrd` might be missing for `T`
```
-在 `largest` 函数体中我们想要使用大于运算符比较两个 `T` 类型的值。这个运算符被定义为标准库中 trait `std::cmp::PartialOrd` 的一个默认方法。所以为了能够使用大于运算符,需要在 `T` 的 trait bounds 中指定 `PartialOrd`,这样 `largest` 函数可以用于任何可以比较大小的类型的 slice。因为 `PartialOrd` 位于 prelude 中所以并不需要手动将其引入作用域。
+在 `largest` 函数体中我们想要使用大于运算符(`>`)比较两个 `T` 类型的值。这个运算符被定义为标准库中 trait `std::cmp::PartialOrd` 的一个默认方法。所以需要在 `T` 的 trait bound 中指定 `PartialOrd`,这样 `largest` 函数可以用于任何可以比较大小的类型的 slice。因为 `PartialOrd` 位于 prelude 中所以并不需要手动将其引入作用域。将 `largest` 的签名修改为如下:
```rust,ignore
fn largest(list: &[T]) -> T {
```
-但是如果编译代码的话,会出现不同的错误:
+但是如果编译代码的话,会出现一些不同的错误:
```text
-error[E0508]: cannot move out of type `[T]`, a non-copy array
- --> src/main.rs:4:23
+error[E0508]: cannot move out of type `[T]`, a non-copy slice
+ --> src/main.rs:2:23
|
-4 | let mut largest = list[0];
- | ----------- ^^^^^^^ cannot move out of here
- | |
- | hint: to prevent move, use `ref largest` or `ref mut largest`
+2 | let mut largest = list[0];
+ | ^^^^^^^
+ | |
+ | cannot move out of here
+ | help: consider using a reference instead: `&list[0]`
error[E0507]: cannot move out of borrowed content
- --> src/main.rs:6:9
+ --> src/main.rs:4:9
|
-6 | for &item in list.iter() {
+4 | for &item in list.iter() {
| ^----
| ||
| |hint: to prevent move, use `ref item` or `ref mut item`
| cannot move out of borrowed content
```
-错误的核心是 `cannot move out of type [T], a non-copy array`,对于非泛型版本的 `largest` 函数,我们只尝试了寻找最大的 `i32` 和 `char`。正如第四章讨论过的,像 `i32` 和 `char` 这样的类型是已知大小的并可以储存在栈上,所以他们实现了 `Copy` trait。当我们将 `largest` 函数改成使用泛型后,现在 `list` 参数的类型就有可能是没有实现 `Copy` trait 的,这意味着我们可能不能将 `list[0]` 的值移动到 `largest` 变量中。
+错误的核心是 `cannot move out of type [T], a non-copy slice`,对于非泛型版本的 `largest` 函数,我们只尝试了寻找最大的 `i32` 和 `char`。正如第四章 “只在栈上的数据:拷贝” 部分讨论过的,像 `i32` 和 `char` 这样的类型是已知大小的并可以储存在栈上,所以他们实现了 `Copy` trait。当我们将 `largest` 函数改成使用泛型后,现在 `list` 参数的类型就有可能是没有实现 `Copy` trait 的。这意味着我们可能不能将 `list[0]` 的值移动到 `largest` 变量中,这导致了上面的错误。
-如果只想对实现了 `Copy` 的类型调用这些代码,可以在 `T` 的 trait bounds 中增加 `Copy`!示例 10-16 中展示了一个可以编译的泛型版本的 `largest` 函数的完整代码,只要传递给 `largest` 的 slice 值的类型实现了 `PartialOrd` 和 `Copy` 这两个 trait,例如 `i32` 和 `char`:
+为了只对实现了 `Copy` 的类型调用这些代码,可以在 `T` 的 trait bounds 中增加 `Copy`!示例 10-15 中展示了一个可以编译的泛型版本的 `largest` 函数的完整代码,只要传递给 `largest` 的 slice 值的类型实现了 `PartialOrd` **和** `Copy` 这两个 trait,例如 `i32` 和 `char`:
文件名: src/main.rs
@@ -304,13 +363,15 @@ fn main() {
}
```
-示例 10-16:一个可以用于任何实现了 `PartialOrd` 和 `Copy` trait 的泛型的 `largest` 函数
+示例 10-15:一个可以用于任何实现了 `PartialOrd` 和 `Copy` trait 的泛型的 `largest` 函数
+
+如果并不希望限制 `largest` 函数只能用于实现了 `Copy` trait 的类型,我们可以在 `T` 的 trait bounds 中指定 `Clone` 而不是 `Copy`。并克隆 slice 的每一个值使得 `largest` 函数拥有其所有权。使用 `clone` 函数意味着对于类似 `String` 这样拥有堆上数据的类型,会潜在的分配更多堆上空间,而堆分配在涉及大量数据时可能会相当缓慢。
-如果并不希望限制 `largest` 函数只能用于实现了 `Copy` trait 的类型,我们可以在 `T` 的 trait bounds 中指定 `Clone` 而不是 `Copy`,并克隆 slice 的每一个值使得 `largest` 函数拥有其所有权。但是使用 `clone` 函数潜在意味着更多的堆分配,而且堆分配在涉及大量数据时可能会相当缓慢。另一种 `largest` 的实现方式是返回 slice 中一个 `T` 值的引用。如果我们将函数返回值从 `T` 改为 `&T` 并改变函数体使其能够返回一个引用,我们将不需要任何 `Clone` 或 `Copy` 的 trait bounds 而且也不会有任何的堆分配。尝试自己实现这种替代解决方式吧!
+另一种 `largest` 的实现方式是返回在 slice 中 `T` 值的引用。如果我们将函数返回值从 `T` 改为 `&T` 并改变函数体使其能够返回一个引用,我们将不需要任何 `Clone` 或 `Copy` 的 trait bounds 而且也不会有任何的堆分配。尝试自己实现这种替代解决方式吧!
-### 使用 trait bound 有条件的实现方法
+### 使用 trait bound 有条件地实现方法
-通过使用带有 trait bound 的泛型 `impl` 块,可以有条件的只为实现了特定 trait 的类型实现方法。例如,示例 10-17 中的类型 `Pair` 总是实现了 `new` 方法,不过只有 `Pair` 内部的 `T` 类型实现了 `PartialOrd` trait 来允许比较和 `Display` trait 来启用打印,才会实现 `cmp_display`:
+通过使用带有 trait bound 的泛型参数的 `impl` 块,可以有条件地只为那些实现了特定 trait 的类型实现方法。例如,示例 10-16 中的类型 `Pair` 总是实现了 `new` 方法,不过只有那些为 `T` 类型实现了 `PartialOrd` trait (来允许比较) **和** `Display` trait (来启用打印)的 `Pair` 才会实现 `cmp_display` 方法:
```rust
use std::fmt::Display;
@@ -342,7 +403,7 @@ impl Pair {
示例 10-17:根据 trait bound 在泛型上有条件的实现方法
-也可以对任何实现了特定 trait 的类型有条件的实现 trait。对任何满足特定 trait bound 的类型实现 trait 被称为 *blanket implementations*,他们被广泛的用于 Rust 标准库中。例如,标准库为任何实现了 `Display` trait 的类型实现了 `ToString` trait。这个 `impl` 块看起来像这样:
+也可以对任何实现了特定 trait 的类型有条件地实现 trait。对任何满足特定 trait bound 的类型实现 trait 被称为 *blanket implementations*,他们被广泛的用于 Rust 标准库中。例如,标准库为任何实现了 `Display` trait 的类型实现了 `ToString` trait。这个 `impl` 块看起来像这样:
```rust,ignore
impl ToString for T {
diff --git a/src/ch10-03-lifetime-syntax.md b/src/ch10-03-lifetime-syntax.md
index 52e8eb5..78f75c3 100644
--- a/src/ch10-03-lifetime-syntax.md
+++ b/src/ch10-03-lifetime-syntax.md
@@ -1,20 +1,18 @@
## 生命周期与引用有效性
-> [ch10-03-lifetime-syntax.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch10-03-lifetime-syntax.md)
+> [ch10-03-lifetime-syntax.md](https://github.com/rust-lang/book/blob/master/src/ch10-03-lifetime-syntax.md)
>
-> commit fa0e4403f8350287b034c5b64af752f647ebb5a2
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
-当在第四章讨论引用时,我们遗漏了一个重要的细节:Rust 中的每一个引用都有其 **生命周期**(*lifetime*),也就是引用保持有效的作用域。大部分时候生命周期是隐含并可以推断的,正如大部分时候类型也是可以推断的一样。类似于当因为有多种可能类型的时候必须注明类型,也会出现引用的生命周期以一些不同方式相关联的情况,所以 Rust 需要我们使用泛型生命周期参数来注明他们的关系,这样就能确保运行时实际使用的引用绝对是有效的。
+当在第四章讨论 “引用和借用” 部分时,我们遗漏了一个重要的细节:Rust 中的每一个引用都有其 **生命周期**(*lifetime*),也就是引用保持有效的作用域。大部分时候生命周期是隐含并可以推断的,正如大部分时候类型也是可以推断的一样。类似于当因为有多种可能类型的时候必须注明类型,也会出现引用的生命周期以一些不同方式相关联的情况,所以 Rust 需要我们使用泛型生命周期参数来注明他们的关系,这样就能确保运行时实际使用的引用绝对是有效的。
-好吧,这有点不太寻常,而且也不同于其他语言中使用的工具。生命周期,从某种意义上说,是 Rust 最与众不同的功能。
-
-生命周期是一个很广泛的话题,本章不可能涉及到它全部的内容,所以这里我们会讲到一些通常你可能会遇到的生命周期语法以便你熟悉这个概念。第十九章会包含生命周期所有功能的更高级的内容。
+生命周期的概念某种程度上说不同于其他语言中类似的工具,毫无疑问这是 Rust 最与众不同的功能。虽然本章不可能涉及到它全部的内容,我们会讲到一些通常你可能会遇到的生命周期语法以便你熟悉这个概念。查看第十九章 “高级生命周期” 部分了解更多的细节。
### 生命周期避免了悬垂引用
-生命周期的主要目标是避免悬垂引用,它会导致程序引用了非预期引用的数据。考虑一下示例 10-18 中的程序,它有一个外部作用域和一个内部作用域,外部作用域声明了一个没有初值的变量 `r`,而内部作用域声明了一个初值为 5 的变量`x`。在内部作用域中,我们尝试将 `r` 的值设置为一个 `x` 的引用。接着在内部作用域结束后,尝试打印出 `r` 的值:
+生命周期的主要目标是避免悬垂引用,它会导致程序引用了非预期引用的数据。考虑一下示例 10-17 中的程序,它有一个外部作用域和一个内部作用域.
-```rust,ignore
+```rust,ignore,does_not_compile
{
let r;
@@ -27,16 +25,15 @@
}
```
-示例 10-18:尝试使用离开作用域的值的引用
+示例 10-17:尝试使用离开作用域的值的引用
-> #### 未初始化变量不能被使用
->
-> 接下来的一些例子中声明了没有初始值的变量,以便这些变量存在于外部作用域。这看起来好像和 Rust 不允许存在空值相冲突。然而这是可以的,如果我们尝试在给它一个值之前使用这个变量,会出现一个编译时错误。请自行尝试!
+> 注意:示例 10-17、10-18 和 10-24 中声明了没有初始值的变量,所以这些变量存在于外部作用域。这乍看之下好像和 Rust 不允许存在空值相冲突。然而如果尝试在给它一个值之前使用这个变量,会出现一个编译时错误,这就说明了 Rust 确实不允许空值。
-当编译这段代码时会得到一个错误:
+外部作用域声明了一个没有初值的变量 `r`,而内部作用域声明了一个初值为 5 的变量`x`。在内部作用域中,我们尝试将 `r` 的值设置为一个 `x` 的引用。接着在内部作用域结束后,尝试打印出 `r` 的值。这段代码不能编译因为 `r` 引用的值在尝试使用之前就离开了作用域。如下是错误信息:
```text
-error: `x` does not live long enough
+error[E0597]: `x` does not live long enough
+ --> src/main.rs:7:5
|
6 | r = &x;
| - borrow occurs here
@@ -47,51 +44,43 @@ error: `x` does not live long enough
| - borrowed value needs to live until here
```
-变量 `x` 并没有 “存在的足够久”。为什么呢?好吧,`x` 在到达第 7 行的大括号的结束时就离开了作用域,这也是内部作用域的结尾。不过 `r` 在外部作用域也是有效的;作用域越大我们就说它 “存在的越久”。如果 Rust 允许这段代码工作,`r` 将会引用在 `x` 离开作用域时被释放的内存,这时尝试对 `r` 做任何操作都会不能正常工作。那么 Rust 是如何决定这段代码是不被允许的呢?
+变量 `x` 并没有 “存在的足够久”。其原因是 `x` 在到达第 7 行内部作用域结束时就离开了作用域。不过 `r` 在外部作用域仍是有效的;作用域越大我们就说它 “存在的越久”。如果 Rust 允许这段代码工作,`r` 将会引用在 `x` 离开作用域时被释放的内存,这时尝试对 `r` 做任何操作都不能正常工作。那么 Rust 是如何决定这段代码是不被允许的呢?这得益于借用检查器。
#### 借用检查器
-编译器的这一部分叫做 **借用检查器**(*borrow checker*),它比较作用域来确保所有的借用都是有效的。示例 10-19 展示了与示例 10-18 相同的例子不过带有变量生命周期的注释:
+Rust 编译器有一个 **借用检查器**(*borrow checker*),它比较作用域来确保所有的借用都是有效的。示例 10-18 展示了与示例 10-17 相同的例子不过带有变量生命周期的注释:
-```rust,ignore
+```rust,ignore,does_not_compile
{
- let r; // -------+-- 'a
- // |
- { // |
- let x = 5; // -+-----+-- 'b
- r = &x; // | |
- } // -+ |
- // |
- println!("r: {}", r); // |
-} // -------+
+ let r; // ---------+-- 'a
+ // |
+ { // |
+ let x = 5; // -+-- 'b |
+ r = &x; // | |
+ } // -+ |
+ // |
+ println!("r: {}", r); // |
+} // ---------+
```
-示例 10-19:`r` 和 `x` 的生命周期注解,分别叫做 `'a` 和 `'b`
+示例 10-18:`r` 和 `x` 的生命周期注解,分别叫做 `'a` 和 `'b`
-
-
+这里将 `r` 的生命周期标记为 `'a` 并将 `x` 的生命周期标记为 `'b`。如你所见,内部的 `'b` 块要比外部的生命周期 `'a` 小得多。在编译时,Rust 比较这两个生命周期的大小,并发现 `r` 拥有生命周期 `'a`,不过它引用了一个拥有生命周期 `'b` 的对象。程序被拒绝编译,因为生命周期 `'b` 比生命周期 `'a` 要小:被引用的对象比它的引用者存在的时间更短。
-我们将 `r` 的生命周期标记为 `'a` 并将 `x` 的生命周期标记为 `'b`。如你所见,内部的 `'b` 块要比外部的生命周期 `'a` 小得多。在编译时,Rust 比较这两个生命周期的大小,并发现 `r` 拥有生命周期 `'a`,不过它引用了一个拥有生命周期 `'b` 的对象。程序被拒绝编译,因为生命周期 `'b` 比生命周期 `'a` 要小:被引用的对象比它的引用者存在的时间更短。
-
-让我们看看示例 10-20 中这个并没有产生悬垂引用且可以正确编译的例子:
+让我们看看示例 10-19 中这个并没有产生悬垂引用且可以正确编译的例子:
```rust
{
- let x = 5; // -----+-- 'b
- // |
- let r = &x; // --+--+-- 'a
- // | |
- println!("r: {}", r); // | |
- // --+ |
-} // -----+
+ let x = 5; // ----------+-- 'b
+ // |
+ let r = &x; // --+-- 'a |
+ // | |
+ println!("r: {}", r); // | |
+ // --+ |
+} // ----------+
```
-示例 10-20:一个有效的引用,因为数据比引用有着更长的生命周期
+示例 10-19:一个有效的引用,因为数据比引用有着更长的生命周期
这里 `x` 拥有生命周期 `'b`,比 `'a` 要大。这就意味着 `r` 可以引用 `x`:Rust 知道 `r` 中的引用在 `x` 有效的时候也总是有效的。
@@ -99,7 +88,7 @@ looking arrows and labels? /Carol -->
### 函数中的泛型生命周期
-让我们来编写一个返回两个字符串 slice 中较长者的函数。我们希望能够通过传递两个字符串 slice 来调用这个函数,并希望返回一个字符串 slice。一旦我们实现了 `longest` 函数,示例 10-21 中的代码应该会打印出 `The longest string is abcd`:
+让我们来编写一个返回两个字符串 slice 中较长者的函数。这个函数获取两个字符串 slice 并返回一个字符串 slice。一旦我们实现了 `longest` 函数,示例 10-20 中的代码应该会打印出 `The longest string is abcd`:
文件名: src/main.rs
@@ -113,34 +102,15 @@ fn main() {
}
```
-示例 10-21:`main` 函数调用 `longest` 函数来寻找两个字符串 slice 中较长的一个
-
-注意函数期望获取字符串 slice(如第四章所讲到的这是引用)因为我们并不希望`longest` 函数获取其参数的所有权。我们希望函数能够接受 `String` 的 slice(也就是变量 `string1` 的类型)以及字符串字面值(也就是变量 `string2` 包含的值)。
-
-
-
+示例 10-20:`main` 函数调用 `longest` 函数来寻找两个字符串 slice 中较长的一个
参考之前第四章中的 “字符串 slice 作为参数” 部分中更多关于为什么上面例子中的参数正符合我们期望的讨论。
-如果尝试像示例 10-22 中那样实现 `longest` 函数,它并不能编译:
+如果尝试像示例 10-21 中那样实现 `longest` 函数,它并不能编译:
文件名: src/main.rs
-```rust,ignore
+```rust,ignore,does_not_compile
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() {
x
@@ -150,43 +120,44 @@ fn longest(x: &str, y: &str) -> &str {
}
```
-示例 10-22:一个 `longest` 函数的实现,它返回两个字符串 slice 中较长者,现在还不能编译
+示例 10-21:一个 `longest` 函数的实现,它返回两个字符串 slice 中较长者,现在还不能编译
-将会出现如下有关生命周期的错误:
+相应地会出现如下有关生命周期的错误:
```text
error[E0106]: missing lifetime specifier
- |
-1 | fn longest(x: &str, y: &str) -> &str {
- | ^ expected lifetime parameter
- |
- = help: this function's return type contains a borrowed value, but the
- signature does not say whether it is borrowed from `x` or `y`
+ --> src/main.rs:1:33
+ |
+1 | fn longest(x: &str, y: &str) -> &str {
+ | ^ expected lifetime parameter
+ |
+ = help: this function's return type contains a borrowed value, but the
+signature does not say whether it is borrowed from `x` or `y`
```
-提示文本告诉我们返回值需要一个泛型生命周期参数,因为 Rust 并不知道将要返回的引用是指向 `x` 或 `y`。事实上我们也不知道,因为函数体中 `if` 块返回一个 `x` 的引用而 `else` 块返回一个 `y` 的引用。
+提示文本揭示了返回值需要一个泛型生命周期参数,因为 Rust 并不知道将要返回的引用是指向 `x` 或 `y`。事实上我们也不知道,因为函数体中 `if` 块返回一个 `x` 的引用而 `else` 块返回一个 `y` 的引用!
-虽然我们定义了这个函数,但是并不知道传递给函数的具体值,所以也不知道到底是 `if` 还是 `else` 会被执行。我们也不知道传入的引用的具体生命周期,所以也就不能像示例 10-19 和 10-20 那样通过观察作用域来确定返回的引用是否总是有效。借用检查器自身同样也无法确定,因为它不知道 `x` 和 `y` 的生命周期是如何与返回值的生命周期相关联的。接下来我们将增加泛型生命周期参数来定义引用间的关系以便借用检查器可以进行分析。
+当我们定义这个函数的时候,并不知道传递给函数的具体值,所以也不知道到底是 `if` 还是 `else` 会被执行。我们也不知道传入的引用的具体生命周期,所以也就不能像示例 10-18 和 10-19 那样通过观察作用域来确定返回的引用是否总是有效。借用检查器自身同样也无法确定,因为它不知道 `x` 和 `y` 的生命周期是如何与返回值的生命周期相关联的。为了修复这个错误,我们将增加泛型生命周期参数来定义引用间的关系以便借用检查器可以进行分析。
### 生命周期注解语法
-生命周期注解并不改变任何引用的生命周期的长短。与当函数签名中指定了泛型类型参数后就可以接受任何类型一样,当指定了泛型生命周期后函数也能接受任何生命周期的引用。生命周期注解所做的就是将多个引用的生命周期联系起来。
+生命周期注解并不改变任何引用的生命周期的长短。与当函数签名中指定了泛型类型参数后就可以接受任何类型一样,当指定了泛型生命周期后函数也能接受任何生命周期的引用。生命周期注解描述了多个引用生命周期相互的关系,而不影响其生命周期。
-生命周期注解有着一个不太常见的语法:生命周期参数名称必须以撇号(`'`)开头。生命周期参数的名称通常全是小写,而且类似于泛型类型,其名称通常非常短。`'a` 是大多数人默认使用的名称。生命周期参数注解位于引用的 `&` 之后,并有一个空格来将引用类型与生命周期注解分隔开。
+生命周期注解有着一个不太常见的语法:生命周期参数名称必须以撇号(`'`)开头,其名称通常全是小写,类似于泛型其名称非常短。`'a` 是大多数人默认使用的名称。生命周期参数注解位于引用的 `&` 之后,并有一个空格来将引用类型与生命周期注解分隔开。
这里有一些例子:我们有一个没有生命周期参数的 `i32` 的引用,一个有叫做 `'a` 的生命周期参数的 `i32` 的引用,和一个生命周期也是 `'a` 的 `i32` 的可变引用:
```rust,ignore
-&i32 // a reference
-&'a i32 // a reference with an explicit lifetime
-&'a mut i32 // a mutable reference with an explicit lifetime
+&i32 // 引用
+&'a i32 // 带有显式生命周期的引用
+&'a mut i32 // 带有显式生命周期的可变引用
```
-单个的生命周期注解本身没有多少意义:生命周期注解告诉 Rust 多个引用的泛型生命周期参数如何相互联系。如果函数有一个生命周期 `'a` 的 `i32` 的引用的参数 `first`,还有另一个同样是生命周期 `'a` 的 `i32` 的引用的参数 `second`,这两个生命周期注解有相同的名称意味着 `first` 和 `second` 必须与这相同的泛型生命周期存在得一样久。
+单个的生命周期注解本身没有多少意义,因为生命周期注解告诉 Rust 多个引用的泛型生命周期参数如何相互联系的。例如如果函数有一个生命周期 `'a` 的 `i32` 的引用的参数 `first`。还有另一个同样是生命周期 `'a` 的 `i32` 的引用的参数 `second`。这两个生命周期注解意味着引用 `first` 和 `second` 必须与这泛型生命周期存在得一样久。
### 函数签名中的生命周期注解
-来看看我们编写的 `longest` 函数的上下文中的生命周期。就像泛型类型参数,泛型生命周期参数需要声明在函数名和参数列表间的尖括号中。这里我们想要告诉 Rust 关于参数中的引用和返回值之间的限制是他们都必须拥有相同的生命周期,就像示例 10-23 中在每个引用中都加上了 `'a` 那样:
+现在来看看 `longest` 函数的上下文中的生命周期。就像泛型类型参数,泛型生命周期参数需要声明在函数名和参数列表间的尖括号中。这里我们想要告诉 Rust 关于参数中的引用和返回值之间的限制是他们都必须拥有相同的生命周期,就像示例 10-22 中在每个引用中都加上了 `'a` 那样:
文件名: src/main.rs
@@ -200,19 +171,17 @@ fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
}
```
-示例 10-23:`longest` 函数定义指定了签名中所有的引用必须有相同的生命周期 `'a`
+示例 10-22:`longest` 函数定义指定了签名中所有的引用必须有相同的生命周期 `'a`
-这段代码能够编译并会产生我们希望得到的示例 10-21 中的 `main` 函数的结果。
+这段代码能够编译并会产生我们希望得到的示例 10-20 中的 `main` 函数的结果。
-现在函数签名表明对于某些生命周期 `'a`,函数会获取两个参数,他们都是与生命周期 `'a` 存在的一样长的字符串 slice。函数会返回一个同样也与生命周期 `'a` 存在的一样长的字符串 slice。这就是我们告诉 Rust 需要其保证的契约。
+现在函数签名表明对于某些生命周期 `'a`,函数会获取两个参数,他们都是与生命周期 `'a` 存在的一样长的字符串 slice。函数会返回一个同样也与生命周期 `'a` 存在的一样长的字符串 slice。这就是我们告诉 Rust 需要其保证的契约。记住通过在函数签名中指定生命周期参数时,我们并没有改变任何传入后返回的值的生命周期。而是指出任何不遵守这个协议的传入值都将被借用检查器拒绝。注意 `longest` 函数并不需要知道 `x` 和 `y` 具体会存在多久,而只需要知道有某个可以被 `'a` 替代的作用域将会满足这个签名。
-通过在函数签名中指定生命周期参数,我们并没有改变任何传入后返回的值的生命周期,而是指出任何不遵守这个协议的传入值都将被借用检查器拒绝。这个函数并不知道(或需要知道)`x` 和 `y` 具体会存在多久,而只需要知道有某个可以被 `'a` 替代的作用域将会满足这个签名。
+当在函数中使用生命周期注解时,这些注解出现在函数签名中,而不存在于函数体中的任何代码中。这是因为 Rust 能够分析函数中代码而不需要任何协助,不过当函数引用或被函数之外的代码引用时,让 Rust 自身分析出参数或返回值的生命周期几乎是不可能的。这些生命周期在每次函数被调用时都可能不同。这也就是为什么我们需要手动标记生命周期。
-当在函数中使用生命周期注解时,这些注解出现在函数签名中,而不存在于函数体中的任何代码中。这是因为 Rust 能够分析函数中代码而不需要任何协助,不过当函数引用或被函数之外的代码引用时,参数或返回值的生命周期可能在每次函数被调用时都不同。这可能会产生惊人的消耗并且对于 Rust 来说通常是不可能分析的。在这种情况下,我们需要自己标注生命周期。
+当具体的引用被传递给 `longest` 时,被 `'a` 所替代的具体生命周期是 `x` 的作用域与 `y` 的作用域相重叠的那一部分。换一种说法就是泛型生命周期 `'a` 的具体生命周期等同于 `x` 和 `y` 的生命周期中较小的那一个。因为我们用相同的生命周期参数 `'a` 标注了返回的引用值,所以返回的引用值就能保证在 `x` 和 `y` 中较短的那个生命周期结束之前保持有效。
-当具体的引用被传递给 `longest` 时,被 `'a` 所替代的具体生命周期是 `x` 的作用域与 `y` 的作用域相重叠的那一部分。因为作用域总是嵌套的,所以换一种说法就是泛型生命周期 `'a` 的具体生命周期等同于 `x` 和 `y` 的生命周期中较小的那一个。因为我们用相同的生命周期参数 `'a` 标注了返回的引用值,所以返回的引用值就能保证在 `x` 和 `y` 中较短的那个生命周期结束之前保持有效。
-
-让我们看看如何通过传递拥有不同具体生命周期的引用来限制 `longest` 函数的使用。示例 10-24 是一个应该在任何编程语言中都很直观的例子:`string1` 直到外部作用域结束都是有效的,`string2` 则在内部作用域中是有效的,而 `result` 则引用了一些直到内部作用域结束都是有效的值。借用检查器认可这些代码;它能够编译和运行,并打印出 `The longest string is long string is long`:
+让我们看看如何通过传递拥有不同具体生命周期的引用来限制 `longest` 函数的使用。示例 10-23 是一个很直观的例子。
文件名: src/main.rs
@@ -236,13 +205,15 @@ fn main() {
}
```
-示例 10-24:通过拥有不同的具体生命周期的 `String` 值调用 `longest` 函数
+示例 10-23:通过拥有不同的具体生命周期的 `String` 值调用 `longest` 函数
+
+在这个例子中,`string1` 直到外部作用域结束都是有效的,`string2` 则在内部作用域中是有效的,而 `result` 则引用了一些直到内部作用域结束都是有效的值。借用检查器认可这些代码;它能够编译和运行,并打印出 `The longest string is long string is long`。
-接下来,让我们尝试一个 `result` 的引用的生命周期肯定比两个参数的要短的例子。将 `result` 变量的声明从内部作用域中移动出来,但是将 `result` 和 `string2` 变量的赋值语句一同留在内部作用域里。接下来,我们将使用 `result` 的 `println!` 移动到内部作用域之外,就在其结束之后。注意示例 10-25 中的代码不能编译:
+接下来,让我们尝试另外一个例子,该例子揭示了 `result` 的引用的生命周期必须是两个参数中较短的那个。以下代码将 `result` 变量的声明移动出内部作用域,但是将 `result` 和 `string2` 变量的赋值语句一同留在内部作用域中。接着,使用了变量 `result` 的 `println!` 也被移动到内部作用域之外。注意示例 10-24 中的代码不能通过编译:
文件名: src/main.rs
-```rust,ignore
+```rust,ignore,does_not_compile
fn main() {
let string1 = String::from("long string is long");
let result;
@@ -254,31 +225,32 @@ fn main() {
}
```
-示例 10-25:在 `string2` 离开作用域之后使用 `result` 的尝试不能编译
+示例 10-24:尝试在 `string2` 离开作用域之后使用 `result`
如果尝试编译会出现如下错误:
```text
-error: `string2` does not live long enough
+error[E0597]: `string2` does not live long enough
+ --> src/main.rs:15:5
|
-6 | result = longest(string1.as_str(), string2.as_str());
+14 | result = longest(string1.as_str(), string2.as_str());
| ------- borrow occurs here
-7 | }
+15 | }
| ^ `string2` dropped here while still borrowed
-8 | println!("The longest string is {}", result);
-9 | }
+16 | println!("The longest string is {}", result);
+17 | }
| - borrowed value needs to live until here
```
错误表明为了保证 `println!` 中的 `result` 是有效的,`string2` 需要直到外部作用域结束都是有效的。Rust 知道这些是因为(`longest`)函数的参数和返回值都使用了相同的生命周期参数 `'a`。
-以人类的理解 `string1` 更长,因此 `result` 会包含指向 `string1` 的引用。因为 `string1` 尚未离开作用域,对于 `println!` 来说 `string1` 的引用仍然是有效的。然而,我们通过生命周期参数告诉 Rust 的是 `longest` 函数返回的引用的生命周期应该与传入参数的生命周期中较短那个保持一致。因此,借用检查器不允许示例 10-25 中的代码,因为它可能会存在无效的引用。
+如果从人的角度读上述代码,我们可能会觉得这个代码是正确的。 `string1` 更长,因此 `result` 会包含指向 `string1` 的引用。因为 `string1` 尚未离开作用域,对于 `println!` 来说 `string1` 的引用仍然是有效的。然而,我们通过生命周期参数告诉 Rust 的是: `longest` 函数返回的引用的生命周期应该与传入参数的生命周期中较短那个保持一致。因此,借用检查器不允许示例 10-24 中的代码,因为它可能会存在无效的引用。
请尝试更多采用不同的值和不同生命周期的引用作为 `longest` 函数的参数和返回值的实验。并在开始编译前猜想你的实验能否通过借用检查器,接着编译一下看看你的理解是否正确!
### 深入理解生命周期
-指定生命周期参数的正确方式依赖函数具体的功能。例如,如果将 `longest` 函数的实现修改为总是返回第一个参数而不是最长的字符串 slice,就不需要为参数 `y` 指定一个生命周期。如下代码将能够编译:
+指定生命周期参数的正确方式依赖函数实现的具体功能。例如,如果将 `longest` 函数的实现修改为总是返回第一个参数而不是最长的字符串 slice,就不需要为参数 `y` 指定一个生命周期。如下代码将能够编译:
文件名: src/main.rs
@@ -294,7 +266,7 @@ fn longest<'a>(x: &'a str, y: &str) -> &'a str {
文件名: src/main.rs
-```rust,ignore
+```rust,ignore,does_not_compile
fn longest<'a>(x: &str, y: &str) -> &'a str {
let result = String::from("really long string");
result.as_str()
@@ -304,27 +276,32 @@ fn longest<'a>(x: &str, y: &str) -> &'a str {
即便我们为返回值指定了生命周期参数 `'a`,这个实现却编译失败了,因为返回值的生命周期与参数完全没有关联。这里是会出现的错误信息:
```text
-error: `result` does not live long enough
+error[E0597]: `result` does not live long enough
+ --> src/main.rs:3:5
|
3 | result.as_str()
| ^^^^^^ does not live long enough
4 | }
| - borrowed value only lives until here
|
-note: borrowed value must be valid for the lifetime 'a as defined on the block
-at 1:44...
+note: borrowed value must be valid for the lifetime 'a as defined on the
+function body at 1:1...
+ --> src/main.rs:1:1
|
-1 | fn longest<'a>(x: &str, y: &str) -> &'a str {
- | ^
+1 | / fn longest<'a>(x: &str, y: &str) -> &'a str {
+2 | | let result = String::from("really long string");
+3 | | result.as_str()
+4 | | }
+ | |_^
```
出现的问题是 `result` 在 `longest` 函数的结尾将离开作用域并被清理,而我们尝试从函数返回一个 `result` 的引用。无法指定生命周期参数来改变悬垂引用,而且 Rust 也不允许我们创建一个悬垂引用。在这种情况,最好的解决方案是返回一个有所有权的数据类型而不是一个引用,这样函数调用者就需要负责清理这个值了。
-从结果上看,生命周期语法是关于如何联系函数不同参数和返回值的生命周期的。一旦他们形成了某种联系,Rust 就有了足够的信息来允许内存安全的操作并阻止会产生悬垂指针亦或是违反内存安全的行为。
+综上,生命周期语法是用于将函数的多个参数与其返回值的生命周期进行关联的。一旦他们形成了某种关联,Rust 就有了足够的信息来允许内存安全的操作并阻止会产生悬垂指针亦或是违反内存安全的行为。
### 结构体定义中的生命周期注解
-目前为止,我们只定义过有所有权类型的结构体。也可以定义存放引用的结构体,不过需要为结构体定义中的每一个引用添加生命周期注解。示例 10-26 中有一个存放了一个字符串 slice 的结构体 `ImportantExcerpt`:
+目前为止,我们只定义过有所有权类型的结构体。接下来,我们将定义包含引用的结构体,不过这需要为结构体定义中的每一个引用添加生命周期注解。示例 10-25 中有一个存放了一个字符串 slice 的结构体 `ImportantExcerpt`:
文件名: src/main.rs
@@ -342,15 +319,15 @@ fn main() {
}
```
-示例 10-26:一个存放引用的结构体,所以其定义需要生命周期注解
+示例 10-25:一个存放引用的结构体,所以其定义需要生命周期注解
-这个结构体有一个字段,`part`,它存放了一个字符串 slice,这是一个引用。类似于泛型参数类型,必须在结构体名称后面的尖括号中声明泛型生命周期参数,以便在结构体定义中使用生命周期参数。
+这个结构体有一个字段,`part`,它存放了一个字符串 slice,这是一个引用。类似于泛型参数类型,必须在结构体名称后面的尖括号中声明泛型生命周期参数,以便在结构体定义中使用生命周期参数。这个注解意味着 `ImportantExcerpt` 的示例不能比其 `part` 字段中的引用存在的更久。
-这里的 `main` 函数创建了一个 `ImportantExcerpt` 的实例,它存放了变量 `novel` 所拥有的 `String` 的第一个句子的引用。
+这里的 `main` 函数创建了一个 `ImportantExcerpt` 的实例,它存放了变量 `novel` 所拥有的 `String` 的第一个句子的引用。`novel` 的数据在 `ImportantExcerpt` 实例创建之前就存在。另外,直到 `ImportantExcerpt` 离开作用域之后 `novel` 都不会离开作用域,所以 `ImportantExcerpt` 实例中的引用是有效的
### 生命周期省略(Lifetime Elision)
-在这一部分,我们知道了每一个引用都有一个生命周期,而且需要为使用了引用的函数或结构体指定生命周期。然而,第四章的 “字符串 slice” 部分有一个函数,我们在示例 10-27 中再次展示出来,它没有生命周期注解却能成功编译:
+现在我们已经知道了每一个引用都有一个生命周期,而且我们需要为那些使用了引用的函数或结构体指定生命周期。然而,第四章的示例 4-9 中有一个函数,如示例 10-26 所示,它没有生命周期注解却能编译成功:
文件名: src/lib.rs
@@ -368,9 +345,9 @@ fn first_word(s: &str) -> &str {
}
```
-示例 10-27:第四章定义了一个没有使用生命周期注解的函数,即便其参数和返回值都是引用
+示例 10-26:示例 4-9 定义了一个没有使用生命周期注解的函数,即便其参数和返回值都是引用
-这个函数没有生命周期注解却能编译是由于一些历史原因:在早期 pre-1.0 版本的 Rust 中,这的确是不能编译的。每一个引用都必须有明确的生命周期。那时的函数签名将会写成这样:
+这个函数没有生命周期注解却能编译是由于一些历史原因:在早期版本(pre-1.0)的 Rust 中,这的确是不能编译的。每一个引用都必须有明确的生命周期。那时的函数签名将会写成这样:
```rust,ignore
fn first_word<'a>(s: &'a str) -> &'a str {
@@ -384,23 +361,25 @@ fn first_word<'a>(s: &'a str) -> &'a str {
省略规则并不提供完整的推断:如果 Rust 在明确遵守这些规则的前提下变量的生命周期仍然是模棱两可的话,它不会猜测剩余引用的生命周期应该是什么。在这种情况,编译器会给出一个错误,这可以通过增加对应引用之间相联系的生命周期注解来解决。
-首先,介绍一些定义:函数或方法的参数的生命周期被称为 **输入生命周期**(*input lifetimes*),而返回值的生命周期被称为 **输出生命周期**(*output lifetimes*)。
+函数或方法的参数的生命周期被称为 **输入生命周期**(*input lifetimes*),而返回值的生命周期被称为 **输出生命周期**(*output lifetimes*)。
+
+编译器采用三条规则来判断引用何时不需要明确的注解。第一条规则适用于输入生命周期,后两条规则适用于输出生命周期。如果编译器检查完这三条规则后仍然存在没有计算出生命周期的引用,编译器将会停止并生成错误。
-现在介绍编译器用于判断引用何时不需要明确生命周期注解的规则。第一条规则适用于输入生命周期,后两条规则适用于输出生命周期。如果编译器检查完这三条规则后仍然存在没有计算出生命周期的引用,编译器将会停止并生成错误。
+这些规则适用于 `fn` 定义,以及 `impl` 块。
-1. 每一个是引用的参数都有它自己的生命周期参数。换句话说就是,有一个引用参数的函数有一个生命周期参数:`fn foo<'a>(x: &'a i32)`,有两个引用参数的函数有两个不同的生命周期参数,`fn foo<'a, 'b>(x: &'a i32, y: &'b i32)`,依此类推。
+第一条规则是每一个是引用的参数都有它自己的生命周期参数。换句话说就是,有一个引用参数的函数有一个生命周期参数:`fn foo<'a>(x: &'a i32)`,有两个引用参数的函数有两个不同的生命周期参数,`fn foo<'a, 'b>(x: &'a i32, y: &'b i32)`,依此类推。
-2. 如果只有一个输入生命周期参数,那么它被赋予所有输出生命周期参数:`fn foo<'a>(x: &'a i32) -> &'a i32`。
+第二条规则是如果只有一个输入生命周期参数,那么它被赋予所有输出生命周期参数:`fn foo<'a>(x: &'a i32) -> &'a i32`。
-3. 如果方法有多个输入生命周期参数,不过其中之一因为方法的缘故为 `&self` 或 `&mut self`,那么 `self` 的生命周期被赋给所有输出生命周期参数。这使得方法编写起来更简洁。
+第三条规则是如果方法有多个输入生命周期参数,不过其中之一因为方法的缘故为 `&self` 或 `&mut self`,那么 `self` 的生命周期被赋给所有输出生命周期参数。这使得方法更容易读写,因为只需更少的符号。
-假设我们自己就是编译器并来计算示例 10-25 `first_word` 函数的签名中的引用的生命周期。开始时签名中的引用并没有关联任何生命周期:
+假设我们自己就是编译器。并应用这些规则来计算示例 10-26 中 `first_word` 函数签名中的引用的生命周期。开始时签名中的引用并没有关联任何生命周期:
```rust,ignore
fn first_word(s: &str) -> &str {
```
-接着我们(作为编译器)应用第一条规则,也就是每个引用参数都有其自己的生命周期。我们像往常一样称之为 `'a`,所以现在签名看起来像这样:
+接着编译器应用第一条规则,也就是每个引用参数都有其自己的生命周期。我们像往常一样称之为 `'a`,所以现在签名看起来像这样:
```rust,ignore
fn first_word<'a>(s: &'a str) -> &str {
@@ -414,7 +393,7 @@ fn first_word<'a>(s: &'a str) -> &'a str {
现在这个函数签名中的所有引用都有了生命周期,如此编译器可以继续它的分析而无须程序员标记这个函数签名中的生命周期。
-让我们再看看另一个例子,这次我们从示例 10-22 中没有生命周期参数的 `longest` 函数开始:
+让我们再看看另一个例子,这次我们从示例 10-21 中没有生命周期参数的 `longest` 函数开始:
```rust,ignore
fn longest(x: &str, y: &str) -> &str {
@@ -426,25 +405,17 @@ fn longest(x: &str, y: &str) -> &str {
fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &str {
```
-再来应用第二条规则,它并不适用因为存在多于一个输入生命周期。再来看第三条规则,它同样也不适用因为没有 `self` 参数。然后我们就没有更多规则了,不过还没有计算出返回值的类型的生命周期。这就是为什么在编译示例 10-22 的代码时会出现错误的原因:编译器使用所有已知的生命周期省略规则,不过仍不能计算出签名中所有引用的生命周期。
+再来应用第二条规则,因为函数存在多个输入生命周期,它并不适用于这种情况。再来看第三条规则,它同样也不适用,这是因为没有 `self` 参数。应用了三个规则之后编译器还没有计算出返回值类型的生命周期。这就是为什么在编译示例 10-21 的代码时会出现错误的原因:编译器使用所有已知的生命周期省略规则,仍不能计算出签名中所有引用的生命周期。
因为第三条规则真正能够适用的就只有方法签名,现在就让我们看看那种情况中的生命周期,并看看为什么这条规则意味着我们经常不需要在方法签名中标注生命周期。
### 方法定义中的生命周期注解
-
-
-
-当为带有生命周期的结构体实现方法时,其语法依然类似示例 10-11 中展示的泛型类型参数的语法:声明和使用生命周期参数的位置依赖于生命周期参数是否同结构体字段或方法参数和返回值相关。
+当为带有生命周期的结构体实现方法时,其语法依然类似示例 10-11 中展示的泛型类型参数的语法。声明和使用生命周期参数的位置依赖于生命周期参数是否同结构体字段或方法参数和返回值相关。
(实现方法时)结构体字段的生命周期必须总是在 `impl` 关键字之后声明并在结构体名称之后被使用,因为这些生命周期是结构体类型的一部分。
-`impl` 块里的方法签名中,引用可能与结构体字段中的引用相关联,也可能是独立的。另外,生命周期省略规则也经常让我们无需在方法签名中使用生命周期注解。让我们看看一些使用示例 10-26 中定义的结构体 `ImportantExcerpt` 的例子。
+`impl` 块里的方法签名中,引用可能与结构体字段中的引用相关联,也可能是独立的。另外,生命周期省略规则也经常让我们无需在方法签名中使用生命周期注解。让我们看看一些使用示例 10-25 中定义的结构体 `ImportantExcerpt` 的例子。
首先,这里有一个方法 `level`。其唯一的参数是 `self` 的引用,而且返回值只是一个 `i32`,并不引用任何值:
@@ -481,7 +452,7 @@ impl<'a> ImportantExcerpt<'a> {
### 静态生命周期
-这里有 **一种** 特殊的生命周期值得讨论:`'static`。`'static` 生命周期存活于整个程序期间。所有的字符串字面值都拥有 `'static` 生命周期,我们也可以选择像下面这样标注出来:
+这里有一种特殊的生命周期值得讨论:`'static`,其生命周期存活于整个程序期间。所有的字符串字面值都拥有 `'static` 生命周期,我们也可以选择像下面这样标注出来:
```rust
let s: &'static str = "I have a static lifetime.";
@@ -489,10 +460,7 @@ let s: &'static str = "I have a static lifetime.";
这个字符串的文本被直接储存在程序的二进制文件中而这个文件总是可用的。因此所有的字符串字面值都是 `'static` 的。
-
-
-
-你可能在错误信息的帮助文本中见过使用 `'static` 生命周期的建议,不过将引用指定为 `'static` 之前,思考一下这个引用是否真的在整个程序的生命周期里都有效(或者哪怕你希望它一直有效,如果可能的话)。大部分情况,代码中的问题是尝试创建一个悬垂引用或者可用的生命周期不匹配,请解决这些问题而不是指定一个 `'static` 的生命周期。
+你可能在错误信息的帮助文本中见过使用 `'static` 生命周期的建议,不过将引用指定为 `'static` 之前,思考一下这个引用是否真的在整个程序的生命周期里都有效。你可能会考虑希望它一直有效,如果可能的话。大部分情况,代码中的问题是尝试创建一个悬垂引用或者可用的生命周期不匹配,请解决这些问题而不是指定一个 `'static` 的生命周期。
### 结合泛型类型参数、trait bounds 和生命周期
@@ -513,7 +481,7 @@ fn longest_with_an_announcement<'a, T>(x: &'a str, y: &'a str, ann: T) -> &'a st
}
```
-这个是示例 10-23 中那个返回两个字符串 slice 中较长者的 `longest` 函数,不过带有一个额外的参数 `ann`。`ann` 的类型是泛型 `T`,它可以被放入任何实现了 `where` 从句中指定的 `Display` trait 的类型。这个额外的参数会在函数比较字符串 slice 的长度之前被打印出来,这也就是为什么 `Display` trait bound 是必须的。因为生命周期也是泛型,所以生命周期参数 `'a` 和泛型类型参数 `T` 都位于函数名后的同一尖括号列表中。
+这个是示例 10-22 中那个返回两个字符串 slice 中较长者的 `longest` 函数,不过带有一个额外的参数 `ann`。`ann` 的类型是泛型 `T`,它可以被放入任何实现了 `where` 从句中指定的 `Display` trait 的类型。这个额外的参数会在函数比较字符串 slice 的长度之前被打印出来,这也就是为什么 `Display` trait bound 是必须的。因为生命周期也是泛型,所以生命周期参数 `'a` 和泛型类型参数 `T` 都位于函数名后的同一尖括号列表中。
## 总结
diff --git a/src/ch11-00-testing.md b/src/ch11-00-testing.md
index 96de28c..f57beeb 100644
--- a/src/ch11-00-testing.md
+++ b/src/ch11-00-testing.md
@@ -1,8 +1,8 @@
-# 测试
+# 编写自动化测试
-> [ch11-00-testing.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch11-00-testing.md)
+> [ch11-00-testing.md](https://github.com/rust-lang/book/blob/master/src/ch11-00-testing.md)
>
-> commit 4464eab0892297b83db7134b7ace12762a89b389
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
> Program testing can be a very effective way to show the presence of bugs, but it is hopelessly inadequate for showing their absence.
>
@@ -12,7 +12,9 @@
>
> Edsger W. Dijkstra,【谦卑的程序员】(1972)
-这并不意味着我们不该尽可能测试软件!程序的正确性意味着代码如我们期望的那样运行。Rust 是一个相当注重正确性的编程语言,不过正确性是一个难以证明的复杂主题。Rust 的类型系统在此问题上下了很大的功夫,不过它不可能捕获所有种类的错误。为此,Rust 也在语言本身包含了编写软件测试的支持。
+Edsger W. Dijkstra 在其 1972 年的文章【谦卑的程序员】(“The Humble Programmer”)中说到 “软件测试是证明 bug 存在的有效方法,而证明其不存在时则显得令人绝望的不足。”(“Program testing can be a very effective way to show the presence of bugs, but it is hopelessly inadequate for showing their absence.”)这并不意味着我们不该尽可能地测试软件!
+
+程序的正确性意味着代码如我们期望的那样运行。Rust 是一个相当注重正确性的编程语言,不过正确性是一个难以证明的复杂主题。Rust 的类型系统在此问题上下了很大的功夫,不过它不可能捕获所有种类的错误。为此,Rust 也在语言本身包含了编写软件测试的支持。
例如,我们可以编写一个叫做 `add_two` 的将传递给它的值加二的函数。它的签名有一个整型参数并返回一个整型值。当实现和编译这个函数时,Rust 会进行所有目前我们已经见过的类型检查和借用检查,例如,这些检查会确保我们不会传递 `String` 或无效的引用给这个函数。Rust 所 **不能** 检查的是这个函数是否会准确的完成我们期望的工作:返回参数加二后的值,而不是比如说参数加 10 或减 50 的值!这也就是测试出场的地方。
diff --git a/src/ch11-01-writing-tests.md b/src/ch11-01-writing-tests.md
index 7aba6cb..18cfe25 100644
--- a/src/ch11-01-writing-tests.md
+++ b/src/ch11-01-writing-tests.md
@@ -1,8 +1,8 @@
## 如何编写测试
-> [ch11-01-writing-tests.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch11-01-writing-tests.md)
+> [ch11-01-writing-tests.md](https://github.com/rust-lang/book/blob/master/src/ch11-01-writing-tests.md)
>
-> commit 4464eab0892297b83db7134b7ace12762a89b389
+> commit 820ac357f6cf0e866e5a8e7a9c57dd3e17e9f8ca
Rust 中的测试函数是用来验证非测试代码是否按照期望的方式运行的。测试函数体通常执行如下三种操作:
@@ -19,21 +19,23 @@ Rust 中的测试函数是用来验证非测试代码是否按照期望的方式
第七章当使用 Cargo 新建一个库项目时,它会自动为我们生成一个测试模块和一个测试函数。这有助于我们开始编写测试,因为这样每次开始新项目时不必去查找测试函数的具体结构和语法了。当然你也可以额外增加任意多的测试函数以及测试模块!
-为了理清测试是如何工作的,我们将通过观察那些自动生成的测试模版——尽管它们实际上没有测试任何代码。接着,我们会写一些真正的测试,调用我们编写的代码并断言他们的行为的正确性。
+我们会通过实验那些自动生成的测试模版而不是实际编写测试代码来探索测试如何工作的一些方面。接着,我们会写一些真正的测试,调用我们编写的代码并断言他们的行为的正确性。
让我们创建一个新的库项目 `adder`:
```text
-$ cargo new adder
+$ cargo new adder --lib
Created library `adder` project
$ cd adder
```
+
adder 库中 `src/lib.rs` 的内容应该看起来如示例 11-1 所示:
文件名: src/lib.rs
```rust
+# fn main() {}
#[cfg(test)]
mod tests {
#[test]
@@ -75,15 +77,18 @@ Cargo 编译并运行了测试。在 `Compiling`、`Finished` 和 `Running` 这
因为之前我们并没有将任何测试标记为忽略,所以摘要中会显示 `0 ignored`。我们也没有过滤需要运行的测试,所以摘要中会显示`0 filtered out`。在下一部分 “控制测试如何运行” 会讨论忽略和过滤测试。
-`0 measured` 统计是针对性能测试的。性能测试(benchmark tests)在编写本书时,仍只能用于 Rust 开发版(nightly Rust)。请查看第一章来了解更多 Rust 开发版的信息。
+`0 measured` 统计是针对性能测试的。性能测试(benchmark tests)在编写本书时,仍只能用于 Rust 开发版(nightly Rust)。请查看 [性能测试的文档][bench] 了解更多。
+
+[bench]: https://doc.rust-lang.org/unstable-book/library-features/test.html
-测试输出中以 `Doc-tests adder` 开头的这一部分是所有文档测试的结果。我们现在并没有任何文档测试,不过 Rust 会编译任何在 API 文档中的代码示例。这个功能帮助我们使文档和代码保持同步!在第十四章的 “文档注释” 部分会讲到如何编写文档测试。现在我们将忽略 `Doc-tests` 部分的输出。
+测试输出的中以 `Doc-tests adder` 开头的这一部分是所有文档测试的结果。我们现在并没有任何文档测试,不过 Rust 会编译任何在 API 文档中的代码示例。这个功能帮助我们使文档和代码保持同步!在第十四章的 “文档注释” 部分会讲到如何编写文档测试。现在我们将忽略 `Doc-tests` 部分的输出。
让我们改变测试的名称并看看这如何改变测试的输出。给 `it_works` 函数起个不同的名字,比如 `exploration`,像这样:
文件名: src/lib.rs
```rust
+# fn main() {}
#[cfg(test)]
mod tests {
#[test]
@@ -106,7 +111,8 @@ test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
文件名: src/lib.rs
-```rust
+```rust,panics
+# fn main() {}
#[cfg(test)]
mod tests {
#[test]
@@ -123,7 +129,6 @@ mod tests {
示例 11-3:增加第二个因调用了 `panic!` 而失败的测试
-
再次 `cargo test` 运行测试。输出应该看起来像示例 11-4,它表明 `exploration` 测试通过了而 `another` 失败了:
```text
@@ -162,6 +167,7 @@ error: test failed
文件名: src/lib.rs
```rust
+# fn main() {}
#[derive(Debug)]
pub struct Rectangle {
length: u32,
@@ -182,6 +188,7 @@ impl Rectangle {
文件名: src/lib.rs
```rust
+# fn main() {}
#[cfg(test)]
mod tests {
use super::*;
@@ -198,7 +205,7 @@ mod tests {
示例 11-6:一个 `can_hold` 的测试,检查一个较大的矩形确实能放得下一个较小的矩形
-注意在 `tests` 模块中新增加了一行:`use super::*;`。`tests` 是一个普通的模块,它遵循第七章 “私有性规则” 部分介绍的可见性规则。因为这是一个内部模块,要测试外部模块中的代码,需要将其引入到内部模块的作用域中。这里选择使用全局导入,以便在 `tests` 模块中使用所有在外部模块定义的内容。
+注意在 `tests` 模块中新增加了一行:`use super::*;`。`tests` 是一个普通的模块,它遵循第七章 “私有性规则” 部分介绍的可见性规则。因为这是一个内部模块,要测试外部模块中的代码,需要将其引入到内部模块的作用域中。这里选择使用 glob 全局导入,以便在 `tests` 模块中使用所有在外部模块定义的内容。
我们将测试命名为 `larger_can_hold_smaller`,并创建所需的两个 `Rectangle` 实例。接着调用 `assert!` 宏并传递 `larger.can_hold(&smaller)` 调用的结果作为参数。这个表达式预期会返回 `true`,所以测试应该通过。让我们拭目以待!
@@ -214,6 +221,7 @@ test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
文件名: src/lib.rs
```rust
+# fn main() {}
#[cfg(test)]
mod tests {
use super::*;
@@ -245,7 +253,8 @@ test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
两个通过的测试!现在让我们看看如果引入一个 bug 的话测试结果会发生什么。将 `can_hold` 方法中比较长度时本应使用大于号的地方改成小于号:
-```rust
+```rust,not_desired_behavior
+# fn main() {}
# #[derive(Debug)]
# pub struct Rectangle {
# length: u32,
@@ -284,13 +293,14 @@ test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out
### 使用 `assert_eq!` 和 `assert_ne!` 宏来测试相等
-测试功能的一个常用方法是将需要测试代码的值与期望值做比较,并检查是否相等。可以通过向 `assert!` 宏传递一个使用 `==` 运算符的表达式来做到。不过这个操作实在是太常见了,以至于标准库提供了一对宏来更方便的处理这些操作:`assert_eq!` 和 `assert_ne!`。这两个宏分别比较两个值是相等还是不相等。当断言失败时他们也会打印出这两个值具体是什么,以便于观察测试 **为什么** 失败,而 `assert!` 只会打印出它从 `==` 表达式中得到了 `false` 值,而不是导致 `false` 的两个值。
+测试功能的一个常用方法是将需要测试代码的值与期望值做比较,并检查是否相等。可以通过向 `assert!` 宏传递一个使用 `==` 运算符的表达式来做到。不过这个操作实在是太常见了,以至于标准库提供了一对宏来更方便的处理这些操作 —— `assert_eq!` 和 `assert_ne!`。这两个宏分别比较两个值是相等还是不相等。当断言失败时他们也会打印出这两个值具体是什么,以便于观察测试 **为什么** 失败,而 `assert!` 只会打印出它从 `==` 表达式中得到了 `false` 值,而不是导致 `false` 的两个值。
-示例 11-7 中,让我们编写一个对其参数加二并返回结果的函数 `add_two`。接着使用 `assert_eq!` 宏测试这个函数:
+示例 11-7 中,让我们编写一个对其参数加二并返回结果的函数 `add_two`。接着使用 `assert_eq!` 宏测试这个函数。
文件名: src/lib.rs
```rust
+# fn main() {}
pub fn add_two(a: i32) -> i32 {
a + 2
}
@@ -321,7 +331,8 @@ test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
在代码中引入一个 bug 来看看使用 `assert_eq!` 的测试失败是什么样的。修改 `add_two` 函数的实现使其加 3:
-```rust
+```rust,not_desired_behavior
+# fn main() {}
pub fn add_two(a: i32) -> i32 {
a + 3
}
@@ -353,17 +364,18 @@ test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out
`assert_ne!` 宏在传递给它的两个值不相等时通过,而在相等时失败。在代码按预期运行,我们不确定值 **会** 是什么,不过能确定值绝对 **不会** 是什么的时候,这个宏最有用处。例如,如果一个函数保证会以某种方式改变其输出,不过这种改变方式是由运行测试时是星期几来决定的,这时最好的断言可能就是函数的输出不等于其输入。
-`assert_eq!` 和 `assert_ne!` 宏在底层分别使用了 `==` 和 `!=`。当断言失败时,这些宏会使用调试格式打印出其参数,这意味着被比较的值必需实现了 `PartialEq` 和 `Debug` trait。所有的基本类型和大部分标准库类型都实现了这些 trait。对于自定义的结构体和枚举,需要实现 `PartialEq` 才能断言他们的值是否相等。需要实现 `Debug` 才能在断言失败时打印他们的值。因为这两个 trait 都是派生 trait,如第五章示例 5-12 所提到的,通常可以直接在结构体或枚举上添加 `#[derive(PartialEq, Debug)]` 注解。附录 C 中有更多关于这些和其他派生 trait 的详细信息。
+`assert_eq!` 和 `assert_ne!` 宏在底层分别使用了 `==` 和 `!=`。当断言失败时,这些宏会使用调试格式打印出其参数,这意味着被比较的值必需实现了 `PartialEq` 和 `Debug` trait。所有的基本类型和大部分标准库类型都实现了这些 trait。对于自定义的结构体和枚举,需要实现 `PartialEq` 才能断言他们的值是否相等。需要实现 `Debug` 才能在断言失败时打印他们的值。因为这两个 trait 都是派生 trait,如第五章示例 5-12 所提到的,通常可以直接在结构体或枚举上添加 `#[derive(PartialEq, Debug)]` 注解。附录 C “可派生 trait” 中有更多关于这些和其他派生 trait 的详细信息。
### 自定义失败信息
-你也可以向 `assert!`、`assert_eq!` 和 `assert_ne!` 宏传递一个可选的失败信息参数,可以在测试失败时将自定义失败信息一同打印出来。任何在 `assert!` 的一个必需参数和 `assert_eq!` 和 `assert_ne!` 的两个必需参数之后指定的参数都会传递给 `format!` 宏(在第八章的“使用 `+` 运算符或 `format!` 宏连接字符串”部分讨论过),所以可以传递一个包含 `{}` 占位符的格式字符串和需要放入占位符的值。自定义信息有助于记录断言的意义;当测试失败时就能更好的理解代码出了什么问题。
+你也可以向 `assert!`、`assert_eq!` 和 `assert_ne!` 宏传递一个可选的失败信息参数,可以在测试失败时将自定义失败信息一同打印出来。任何在 `assert!` 的一个必需参数和 `assert_eq!` 和 `assert_ne!` 的两个必需参数之后指定的参数都会传递给 `format!` 宏(在第八章的“使用 `+` 运算符或 `format!` 宏拼接字符串”部分讨论过),所以可以传递一个包含 `{}` 占位符的格式字符串和需要放入占位符的值。自定义信息有助于记录断言的意义;当测试失败时就能更好的理解代码出了什么问题。
例如,比如说有一个根据人名进行问候的函数,而我们希望测试将传递给函数的人名显示在输出中:
文件名: src/lib.rs
```rust
+# fn main() {}
pub fn greeting(name: &str) -> String {
format!("Hello {}!", name)
}
@@ -384,7 +396,8 @@ mod tests {
让我们通过将 `greeting` 改为不包含 `name` 来在代码中引入一个 bug 来测试失败时是怎样的:
-```rust
+```rust,not_desired_behavior
+# fn main() {}
pub fn greeting(name: &str) -> String {
String::from("Hello!")
}
@@ -407,7 +420,7 @@ failures:
tests::greeting_contains_name
```
-这仅仅告诉了我们断言失败了和失败的行号。一个更有用的失败信息应该打印出 `greeting` 函数的值。让我们为测试函数增加一个自定义失败信息参数:带占位符的格式字符串,以及 `greeting` 函数的值:
+结果仅仅告诉了我们断言失败了和失败的行号。一个更有用的失败信息应该打印出 `greeting` 函数的值。让我们为测试函数增加一个自定义失败信息参数:带占位符的格式字符串,以及 `greeting` 函数的值:
```rust,ignore
#[test]
@@ -422,7 +435,6 @@ fn greeting_contains_name() {
现在如果再次运行测试,将会看到更有价值的信息:
-
```text
---- tests::greeting_contains_name stdout ----
thread 'tests::greeting_contains_name' panicked at 'Greeting did not
@@ -434,7 +446,7 @@ note: Run with `RUST_BACKTRACE=1` for a backtrace.
### 使用 `should_panic` 检查 panic
-除了检查代码是否返回期望的正确的值之外,检查代码是否按照期望处理错误也是很重要的。例如,考虑第九章示例 9-9 创建的 `Guess` 类型。其他使用 `Guess` 的代码都是基于 `Guess` 实例仅有的值范围在 1 到 100 的前提。可以编写一个测试来确保创建一个超出范围的值的 `Guess` 实例会 panic。
+除了检查代码是否返回期望的正确的值之外,检查代码是否按照期望处理错误也是很重要的。例如,考虑第九章示例 9-10 创建的 `Guess` 类型。其他使用 `Guess` 的代码都是基于 `Guess` 实例仅有的值范围在 1 到 100 的前提。可以编写一个测试来确保创建一个超出范围的值的 `Guess` 实例会 panic。
可以通过对函数增加另一个属性 `should_panic` 来实现这些。这个属性在函数中的代码 panic 时会通过,而在其中的代码没有 panic 时失败。
@@ -443,12 +455,13 @@ note: Run with `RUST_BACKTRACE=1` for a backtrace.
文件名: src/lib.rs
```rust
+# fn main() {}
pub struct Guess {
- value: u32,
+ value: i32,
}
impl Guess {
- pub fn new(value: u32) -> Guess {
+ pub fn new(value: i32) -> Guess {
if value < 1 || value > 100 {
panic!("Guess value must be between 1 and 100, got {}.", value);
}
@@ -484,15 +497,16 @@ test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
看起来不错!现在在代码中引入 bug,移除 `new` 函数在值大于 100 时会 panic 的条件:
-```rust
+```rust,not_desired_behavior
+# fn main() {}
# pub struct Guess {
-# value: u32,
+# value: i32,
# }
#
// --snip--
impl Guess {
- pub fn new(value: u32) -> Guess {
+ pub fn new(value: i32) -> Guess {
if value < 1 {
panic!("Guess value must be between 1 and 100, got {}.", value);
}
@@ -525,14 +539,15 @@ test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out
文件名: src/lib.rs
```rust
+# fn main() {}
# pub struct Guess {
-# value: u32,
+# value: i32,
# }
#
// --snip--
impl Guess {
- pub fn new(value: u32) -> Guess {
+ pub fn new(value: i32) -> Guess {
if value < 1 {
panic!("Guess value must be greater than or equal to 1, got {}.",
value);
@@ -565,7 +580,7 @@ mod tests {
为了观察带有 `expected` 信息的 `should_panic` 测试失败时会发生什么,让我们再次引入一个 bug,将 `if value < 1` 和 `else if value > 100` 的代码块对换:
-```rust,ignore
+```rust,ignore,not_desired_behavior
if value < 1 {
panic!("Guess value must be less than or equal to 100, got {}.", value);
} else if value > 100 {
@@ -596,4 +611,24 @@ test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out
失败信息表明测试确实如期望 panic 了,不过 panic 信息中并没有包含 `expected` 信息 `'Guess value must be less than or equal to 100'`。而我们得到的 panic 信息是 `'Guess value must be greater than or equal to 1, got 200.'`。这样就可以开始寻找 bug 在哪了!
+### 将 `Result` 用于测试
+
+目前为止,我们编写的测试在失败时就会 panic。也可以使用 `Result` 编写测试!这里是第一个例子采用了 Result:
+
+```rust
+#[cfg(test)]
+mod tests {
+ #[test]
+ fn it_works() -> Result<(), String> {
+ if 2 + 2 == 4 {
+ Ok(())
+ } else {
+ Err(String::from("two plus two does not equal four"))
+ }
+ }
+}
+```
+
+这里我们将 `it_works` 改为返回 Result。同时在函数体中,在成功时返回 `Ok(())` 而不是 `assert_eq!`,而失败时返回带有 `String` 的 `Err`。跟之前一样,这个测试可能成功或失败,不过不再通过 panic,可以通过 `Result` 来判断结果。为此不能在对这些函数使用 `#[should_panic]`;而是应该返回 `Err`!
+
现在你知道了几种编写测试的方法,让我们看看运行测试时会发生什么,和可以用于 `cargo test` 的不同选项。
diff --git a/src/ch11-02-running-tests.md b/src/ch11-02-running-tests.md
index 1de61e2..9792a85 100644
--- a/src/ch11-02-running-tests.md
+++ b/src/ch11-02-running-tests.md
@@ -1,16 +1,16 @@
## 控制测试如何运行
-> [ch11-02-running-tests.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch11-02-running-tests.md)
+> [ch11-02-running-tests.md](https://github.com/rust-lang/book/blob/master/src/ch11-02-running-tests.md)
>
-> commit 550c8ea6f74060ff1f7b67e7e1878c4da121682d
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
就像 `cargo run` 会编译代码并运行生成的二进制文件一样,`cargo test` 在测试模式下编译代码并运行生成的测试二进制文件。可以指定命令行参数来改变 `cargo test` 的默认行为。例如,`cargo test` 生成的二进制文件的默认行为是并行的运行所有测试,并截获测试运行过程中产生的输出,阻止他们被显示出来,使得阅读测试结果相关的内容变得更容易。
-这些选项的一部分可以传递给 `cargo test`,而另一些则需要传递给生成的测试二进制文件。为了分隔两种类型的参数,首先列出传递给 `cargo test` 的参数,接着是分隔符 `--`,再之后是传递给测试二进制文件的参数。运行 `cargo test --help` 会告诉你 `cargo test` 的相关参数,而运行 `cargo test -- --help` 则会告诉你可以在分隔符 `--` 之后使用的相关参数。
+可以将一部分命令行参数传递给 `cargo test`,而将另外一部分传递给生成的测试二进制文件。为了分隔这两种参数,需要先列出传递给 `cargo test` 的参数,接着是分隔符 `--`,再之后是传递给测试二进制文件的参数。运行 `cargo test --help` 会提示 `cargo test` 的有关参数,而运行 `cargo test -- --help` 可以提示在分隔符 `--` 之后使用的有关参数。
### 并行或连续的运行测试
-当运行多个测试时,他们默认使用线程来并行的运行。这意味着测试会更快的运行完毕,所以你可以更快的得到代码能否工作的反馈。因为测试是在同时运行的,你应该确保测试不能相互依赖,或依赖任何共享的状态,包括依赖共享的环境,比如当前工作目录或者环境变量。
+当运行多个测试时, Rust 默认使用线程来并行运行。这意味着测试会更快地运行完毕,所以你可以更快的得到代码能否工作的反馈。因为测试是在同时运行的,你应该确保测试不能相互依赖,或依赖任何共享的状态,包括依赖共享的环境,比如当前工作目录或者环境变量。
举个例子,每一个测试都运行一些代码,假设这些代码都在硬盘上创建一个 *test-output.txt* 文件并写入一些数据。接着每一个测试都读取文件中的数据并断言这个文件包含特定的值,而这个值在每个测试中都是不同的。因为所有测试都是同时运行的,一个测试可能会在另一个测试读写文件过程中修改了文件。那么第二个测试就会失败,并不是因为代码不正确,而是因为测试并行运行时相互干扰。一个解决方案是使每一个测试读写不同的文件;另一个解决方案是一次运行一个测试。
@@ -20,7 +20,7 @@
$ cargo test -- --test-threads=1
```
-这里将测试线程设置为 1,告诉程序不要使用任何并行机制。这也会比并行运行花费更多时间,不过在有共享的状态时,测试就不会潜在的相互干扰了。
+这里将测试线程设置为 `1`,告诉程序不要使用任何并行机制。这也会比并行运行花费更多时间,不过在有共享的状态时,测试就不会潜在的相互干扰了。
### 显示函数输出
@@ -30,7 +30,7 @@ $ cargo test -- --test-threads=1
文件名: src/lib.rs
-```rust
+```rust,panics
fn prints_and_returns_10(a: i32) -> i32 {
println!("I got the value {}", a);
10
@@ -107,13 +107,13 @@ failures:
test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out
```
-注意测试的输出和测试结果的输出是相互交叉的;这是由于测试是并行运行的,也就是上一部分讲到的。尝试一同使用 `--test-threads=1` 和 `--nocapture` 功能来看看输出是什么样子!
+注意测试的输出和测试结果的输出是相互交叉的,这是由于测试是并行运行的(见上一部分)。尝试一同使用 `--test-threads=1` 和 `--nocapture` 功能来看看输出是什么样子!
-### 通过名称来运行测试的子集
+### 通过指定名字来运行部分测试
-有时运行整个测试集会耗费很长时间。如果你负责特定位置的代码,你可能会希望只运行这些代码相关的测试。你可以向 `cargo test` 传递希望运行的测试的部分名称作为参数来选择运行哪些测试。
+有时运行整个测试集会耗费很长时间。如果你负责特定位置的代码,你可能会希望只运行与这些代码相关的测试。你可以向 `cargo test` 传递所希望运行的测试名称的参数来选择运行哪些测试。
-为了展示如何运行测试的子集,示例 11-11 为 `add_two` 函数创建了三个测试,我们可以选择具体运行哪一个:
+为了展示如何运行部分测试,示例 11-11 为 `add_two` 函数创建了三个测试,我们可以选择具体运行哪一个:
文件名: src/lib.rs
@@ -240,4 +240,4 @@ test expensive_test ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out
```
-通过控制运行哪些测试,你可以确保能够快速地运行 `cargo test` 。当某个时刻需要检查 `ignored` 测试的结果,而且你也有时间等待这个结果的话,就可以选择执行 `cargo test -- --ignored`。
+通过控制运行哪些测试,你可以确保能够快速地运行 `cargo test` 。当你需要运行 `ignored` 的测试时,可以执行 `cargo test -- --ignored`。
diff --git a/src/ch11-03-test-organization.md b/src/ch11-03-test-organization.md
index 7903bab..7114330 100644
--- a/src/ch11-03-test-organization.md
+++ b/src/ch11-03-test-organization.md
@@ -1,8 +1,8 @@
## 测试的组织结构
-> [ch11-03-test-organization.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch11-03-test-organization.md)
+> [ch11-03-test-organization.md](https://github.com/rust-lang/book/blob/master/src/ch11-03-test-organization.md)
>
-> commit b3eddb8edc0c3f83647143673d18efac0a44083a
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
本章一开始就提到,测试是一个复杂的概念,而且不同的开发者也采用不同的技术和组织。Rust 社区倾向于根据测试的两个主要分类来考虑问题:**单元测试**(*unit tests*)与 **集成测试**(*integration tests*)。单元测试倾向于更小而更集中,在隔离的环境中一次测试一个模块,或者是测试私有接口。而集成测试对于你的库来说则完全是外部的。它们与其他外部代码一样,通过相同的方式使用你的代码,只测试公有接口而且每个测试都有可能会测试多个模块。
@@ -16,7 +16,7 @@
测试模块的 `#[cfg(test)]` 注解告诉 Rust 只在执行 `cargo test` 时才编译和运行测试代码,而在运行 `cargo build` 时不这么做。这在只希望构建库的时候可以节省编译时间,并且因为它们并没有包含测试,所以能减少编译产生的文件的大小。与之对应的集成测试因为位于另一个文件夹,所以它们并不需要 `#[cfg(test)]` 注解。然而单元测试位于与源码相同的文件中,所以你需要使用 `#[cfg(test)]` 来指定他们不应该被包含进编译结果中。
-还记得本章第一部分新建的 `adder` 项目吗?Cargo 为我们生成了如下代码:
+回忆本章第一部分新建的 `adder` 项目吗,Cargo 为我们生成了如下代码:
文件名: src/lib.rs
@@ -39,6 +39,8 @@ mod tests {
文件名: src/lib.rs
```rust
+# fn main() {}
+
pub fn add_two(a: i32) -> i32 {
internal_adder(a, 2)
}
@@ -75,7 +77,7 @@ mod tests {
文件名: tests/integration_test.rs
```rust,ignore
-extern crate adder;
+use adder;
#[test]
fn it_adds_two() {
@@ -85,9 +87,9 @@ fn it_adds_two() {
示例 11-13:一个 `adder` crate 中函数的集成测试
-我们在顶部增加了 `extern crate adder`,这在单元测试中是不需要的。这是因为每一个 `tests` 目录中的测试文件都是完全独立的 crate,所以需要在每一个文件中导入库。
+与单元测试不同,我们需要在文件顶部添加 `use adder`。这是因为每一个 `tests` 目录中的测试文件都是完全独立的 crate,所以需要在每一个文件中导入库。
-并不需要将 *tests/integration_test.rs* 中的任何代码标注为 `#[cfg(test)]`。Cargo 对 `tests` 文件夹特殊处理并只会在运行 `cargo test` 时编译这个目录中的文件。现在就运行 `cargo test` 试试:
+并不需要将 *tests/integration_test.rs* 中的任何代码标注为 `#[cfg(test)]`。 `tests` 文件夹在 Cargo 中是一个特殊的文件夹, Cargo 只会在运行 `cargo test` 时编译这个目录中的文件。现在就运行 `cargo test` 试试:
```text
$ cargo test
@@ -141,13 +143,13 @@ test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
将每个集成测试文件当作其自己的 crate 来对待,这更有助于创建单独的作用域,这种单独的作用域能提供更类似与最终使用者使用 crate 的环境。然而,正如你在第七章中学习的如何将代码分为模块和文件的知识,*tests* 目录中的文件不能像 *src* 中的文件那样共享相同的行为。
-当你有一些在多个集成测试文件都会用到的帮助函数,而你尝试按照第七章 “将模块移动到其他文件” 部分的步骤将他们提取到一个通用的模块中时, *tests* 目录中不同文件的行为就会显得很明显。例如,如果我们创建了 *tests/common.rs* 文件并将一个名叫 `setup` 的函数放入其中,这里将放入一些我们希望能够在多个测试文件的多个测试函数中调用的代码:
+当你有一些在多个集成测试文件都会用到的帮助函数,而你尝试按照第七章 “将模块移动到其他文件” 部分的步骤将他们提取到一个通用的模块中时, *tests* 目录中不同文件的行为就会显得很明显。例如,如果我们可以创建 一个*tests/common.rs* 文件并创建一个名叫 `setup` 的函数,我们希望这个函数能被多个测试文件的测试函数调用:
文件名: tests/common.rs
```rust
pub fn setup() {
- // setup code specific to your library's tests would go here
+ // 编写特定库测试所需的代码
}
```
@@ -179,16 +181,16 @@ running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
```
-我们并不希望 `common` 出现在测试结果中并显示 `running 0 tests` 。我们只是希望能够在其他集成测试文件中分享一些代码罢了。
+我们并不想要`common` 出现在测试结果中显示 `running 0 tests` 。我们只是希望其能被其他多个集成测试文件中调用罢了。
-为了避免 `common` 出现在测试输出中,我们将创建 *tests/common/mod.rs* ,而不是创建 *tests/common.rs* 。在第七章的 “模块文件系统规则” 部分,对于拥有子模块的模块文件使用了 *module_name/mod.rs* 命名规范,虽然这里 `common` 并没有子模块,但是这样命名告诉 Rust 不要将 `common` 看作一个集成测试文件。当将 `setup` 函数代码移动到 *tests/common/mod.rs* 并删除 *tests/common.rs* 文件之后,测试输出中将不会出现这一部分。*tests* 目录中的子目录不会被作为单独的 crate 编译或作为一个测试结果部分出现在测试输出中。
+为了不让 `common` 出现在测试输出中,我们将创建 *tests/common/mod.rs* ,而不是创建 *tests/common.rs* 。这是一种 Rust 的命名规范,这样命名告诉 Rust 不要将 `common` 看作一个集成测试文件。将 `setup` 函数代码移动到 *tests/common/mod.rs* 并删除 *tests/common.rs* 文件之后,测试输出中将不会出现这一部分。*tests* 目录中的子目录不会被作为单独的 crate 编译或作为一个测试结果部分出现在测试输出中。
一旦拥有了 *tests/common/mod.rs*,就可以将其作为模块以便在任何集成测试文件中使用。这里是一个 *tests/integration_test.rs* 中调用 `setup` 函数的 `it_adds_two` 测试的例子:
文件名: tests/integration_test.rs
```rust,ignore
-extern crate adder;
+use adder;
mod common;
@@ -199,7 +201,7 @@ fn it_adds_two() {
}
```
-注意 `mod common;` 声明与示例 7-4 中展示的模块声明相同。接着在测试函数中就可以调用 `common::setup()` 了。
+注意 `mod common;` 声明与示例 7-25 中展示的模块声明相同。接着在测试函数中就可以调用 `common::setup()` 了。
#### 二进制 crate 的集成测试
diff --git a/src/ch12-00-an-io-project.md b/src/ch12-00-an-io-project.md
index bd640b6..df607a3 100644
--- a/src/ch12-00-an-io-project.md
+++ b/src/ch12-00-an-io-project.md
@@ -1,12 +1,12 @@
# 一个 I/O 项目:构建一个命令行程序
-> [ch12-00-an-io-project.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch12-00-an-io-project.md)
+> [ch12-00-an-io-project.md](https://github.com/rust-lang/book/blob/master/src/ch12-00-an-io-project.md)
>
-> commit 97e60b3cb623d4a5b85419212b085ade8a11cbe1
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
本章既是一个目前所学的很多技能的概括,也是一个更多标准库功能的探索。我们将构建一个与文件和命令行输入/输出交互的命令行工具来练习现在一些你已经掌握的 Rust 技能。
-Rust 的运行速度、安全性、**单二进制文件** 输出和跨平台支持使其成为创建命令行程序的绝佳选择,所以我们的项目将创建一个我们自己版本的经典命令行工具:`grep`。grep 是 “**G**lobally search a **R**egular **E**xpression and **P**rint.” 的首字母缩写。`grep` 最简单的使用场景是在特定文件中搜索指定字符串。为此,`grep` 获取一个文件名和一个字符串作为参数,接着读取文件并找到其中包含字符串参数的行,然后打印出这些行。
+Rust 的运行速度、安全性、单二进制文件输出和跨平台支持使其成为创建命令行程序的绝佳选择,所以我们的项目将创建一个我们自己版本的经典命令行工具:`grep`。grep 是 “**G**lobally search a **R**egular **E**xpression and **P**rint.” 的首字母缩写。`grep` 最简单的使用场景是在特定文件中搜索指定字符串。为此,`grep` 获取一个文件名和一个字符串作为参数,接着读取文件并找到其中包含字符串参数的行,然后打印出这些行。
在这个过程中,我们会展示如何让我们的命令行工具利用很多命令行工具中用到的终端功能。读取环境变量来使得用户可以配置工具的行为。打印到标准错误控制流(`stderr`) 而不是标准输出(`stdout`),例如这样用户可以选择将成功输出重定向到文件中的同时仍然在屏幕上显示错误信息。
diff --git a/src/ch12-01-accepting-command-line-arguments.md b/src/ch12-01-accepting-command-line-arguments.md
index f8b20ab..e82a7fe 100644
--- a/src/ch12-01-accepting-command-line-arguments.md
+++ b/src/ch12-01-accepting-command-line-arguments.md
@@ -1,13 +1,13 @@
-## 接受命令行参数
+## 接受命令行参数
-> [ch12-01-accepting-command-line-arguments.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch12-01-accepting-command-line-arguments.md)
+> [ch12-01-accepting-command-line-arguments.md](https://github.com/rust-lang/book/blob/master/src/ch12-01-accepting-command-line-arguments.md)
>
-> commit 97e60b3cb623d4a5b85419212b085ade8a11cbe1
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
-一如之前使用 `cargo new` 新建一个项目,我们称之为 `minigrep` 以便与可能已经安装在系统上的`grep`工具相区别:
+一如既往使用 `cargo new` 新建一个项目,我们称之为 `minigrep` 以便与可能已经安装在系统上的 `grep` 工具相区别:
```text
-$ cargo new --bin minigrep
+$ cargo new minigrep
Created binary (application) `minigrep` project
$ cd minigrep
```
@@ -37,7 +37,6 @@ fn main() {
}
```
-
示例 12-1:将命令行参数收集到一个 vector 中并打印出来
首先使用 `use` 语句来将 `std::env` 模块引入作用域以便可以使用它的 `args` 函数。注意 `std::env::args` 函数被嵌套进了两层模块中。正如第七章讲到的,当所需函数嵌套了多于一层模块时,通常将父模块引入作用域,而不是其自身。这便于我们利用 `std::env` 中的其他函数。这比增加了 `use std::env::args;` 后仅仅使用 `args` 调用函数要更明确一些,因为 `args` 容易被错认成一个定义于当前模块的函数。
@@ -46,9 +45,9 @@ fn main() {
>
> 注意 `std::env::args` 在其任何参数包含无效 Unicode 字符时会 panic。如果你需要接受包含无效 Unicode 字符的参数,使用 `std::env::args_os` 代替。这个函数返回 `OsString` 值而不是 `String` 值。这里出于简单考虑使用了 `std::env::args`,因为 `OsString` 值每个平台都不一样而且比 `String` 值处理起来更为复杂。
-在 `main` 函数的第一行,我们调用了 `env::args`,并立即使用 `collect` 来创建了一个包含迭代器所有值的 vector。`collect` 可以被用来创建很多类型的集合,所以这里显式注明 `args` 的类型来指定我们需要一个字符串 vector。虽然在 Rust 中我们很少会需要注明类型,`collect` 就是一个经常需要注明类型的函数,因为 Rust 不能推断出你想要什么类型的集合。
+在 `main` 函数的第一行,我们调用了 `env::args`,并立即使用 `collect` 来创建了一个包含迭代器所有值的 vector。`collect` 可以被用来创建很多类型的集合,所以这里显式注明 `args` 的类型来指定我们需要一个字符串 vector。虽然在 Rust 中我们很少会需要注明类型,然而 `collect` 是一个经常需要注明类型的函数,因为 Rust 不能推断出你想要什么类型的集合。
-最后,我们使用调试格式 `:?` 打印出 vector。让我们尝试不用参数运行代码,接着用两个参数:
+最后,我们使用调试格式 `:?` 打印出 vector。让我们尝试分别用两种方式(不包含参数和包含参数)运行代码:
```text
$ cargo run
diff --git a/src/ch12-02-reading-a-file.md b/src/ch12-02-reading-a-file.md
index 7eba5e5..8da0e3c 100644
--- a/src/ch12-02-reading-a-file.md
+++ b/src/ch12-02-reading-a-file.md
@@ -1,18 +1,18 @@
-## 读取文件
+## 读取文件
-> [ch12-02-reading-a-file.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch12-02-reading-a-file.md)
+> [ch12-02-reading-a-file.md](https://github.com/rust-lang/book/blob/master/src/ch12-02-reading-a-file.md)
>
-> commit 97e60b3cb623d4a5b85419212b085ade8a11cbe1
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
-接下来我们将读取由命令行文件名参数指定的文件。首先,需要一个用来测试的示例文件——用来确保 `minigrep` 正常工作的最好的文件是拥有多行少量文本且有一些重复单词的文件。示例 12-3 是一首艾米莉·狄金森(Emily Dickinson)的诗,它正适合这个工作!在项目根目录创建一个文件 `poem.txt`,并输入诗 "I'm nobody! Who are you?":
+现在我们要增加读取由 `filename` 命令行参数指定的文件的功能。首先,需要一个用来测试的示例文件:用来确保 `minigrep` 正常工作的最好的文件是拥有多行少量文本且有一些重复单词的文件。示例 12-3 是一首艾米莉·狄金森(Emily Dickinson)的诗,它正适合这个工作!在项目根目录创建一个文件 `poem.txt`,并输入诗 "I'm nobody! Who are you?":
文件名: poem.txt
```text
-I’m nobody! Who are you?
+I'm nobody! Who are you?
Are you nobody, too?
-Then there’s a pair of us — don’t tell!
-They’d banish us, you know.
+Then there's a pair of us - don't tell!
+They'd banish us, you know.
How dreary to be somebody!
How public, like a frog
@@ -28,8 +28,7 @@ To an admiring bog!
```rust,should_panic
use std::env;
-use std::fs::File;
-use std::io::prelude::*;
+use std::fs;
fn main() {
# let args: Vec = env::args().collect();
@@ -41,11 +40,8 @@ fn main() {
// --snip--
println!("In file {}", filename);
- let mut f = File::open(filename).expect("file not found");
-
- let mut contents = String::new();
- f.read_to_string(&mut contents)
- .expect("something went wrong reading the file");
+ let contents = fs::read_to_string(filename)
+ .expect("Something went wrong reading the file");
println!("With text:\n{}", contents);
}
@@ -53,11 +49,11 @@ fn main() {
示例 12-4:读取第二个参数所指定的文件内容
-首先,我们增加了更多的 `use` 语句来引入标准库中的相关部分:需要 `std::fs::File` 来处理文件,而 `std::io::prelude::*` 则包含许多对于 I/O(包括文件 I/O)有帮助的 trait。类似于 Rust 有一个通用的 prelude 来自动引入特定内容,`std::io` 也有其自己的 prelude 来引入处理 I/O 时所需的通用内容。不同于默认的 prelude,必须显式 `use` 位于 `std::io` 中的 prelude。
+首先,我们增加了一个 `use` 语句来引入标准库中的相关部分:我们需要 `std::fs` 来处理文件。
-在 `main` 中,我们增加了三点内容:第一,通过传递变量 `filename` 的值调用 `File::open` 函数来获取文件的可变句柄。创建了叫做 `contents` 的变量并将其设置为一个可变的,空的 `String`。它将会存放之后读取的文件的内容。第三,对文件句柄调用 `read_to_string` 并传递 `contents` 的可变引用作为参数。
+在 `main` 中新增了一行语句:`fs::read_to_string` 接受 `filename`,打开文件,接着返回包含其内容的 `Result`。
-在这些代码之后,我们再次增加了临时的 `println!` 打印出读取文件后 `contents` 的值,这样就可以检查目前为止的程序能否工作。
+在这些代码之后,我们再次增加了临时的 `println!` 打印出读取文件之后 `contents` 的值,这样就可以检查目前为止的程序能否工作。
尝试运行这些代码,随意指定一个字符串作为第一个命令行参数(因为还未实现搜索功能的部分)而将 *poem.txt* 文件将作为第二个参数:
diff --git a/src/ch12-03-improving-error-handling-and-modularity.md b/src/ch12-03-improving-error-handling-and-modularity.md
index bffb8d9..27bd0a1 100644
--- a/src/ch12-03-improving-error-handling-and-modularity.md
+++ b/src/ch12-03-improving-error-handling-and-modularity.md
@@ -1,8 +1,8 @@
-## 重构改进模块性和错误处理
+## 重构改进模块性和错误处理
-> [ch12-03-improving-error-handling-and-modularity.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch12-03-improving-error-handling-and-modularity.md)
+> [ch12-03-improving-error-handling-and-modularity.md](https://github.com/rust-lang/book/blob/master/src/ch12-03-improving-error-handling-and-modularity.md)
>
-> commit c1fb695e6c9091c9a5145320498ef80a649af33c
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
为了改善我们的程序这里有四个问题需要修复,而且他们都与程序的组织方式和如何处理潜在错误有关。
@@ -10,9 +10,9 @@
这同时也关系到第二个问题:`search` 和 `filename` 是程序中的配置变量,而像 `f` 和 `contents` 则用来执行程序逻辑。随着 `main` 函数的增长,就需要引入更多的变量到作用域中,而当作用域中有更多的变量时,将更难以追踪每个变量的目的。最好能将配置变量组织进一个结构,这样就能使他们的目的更明确了。
-第三个问题是如果打开文件失败我们使用 `expect` 来打印出错误信息,不过这个错误信息只是说 `file not found`。除了缺少文件之外还有很多打开文件可能失败的方式:例如,文件可能存在,不过可能没有打开它的权限。如果我们现在就出于这种情况,打印出的 `file not found` 错误信息就给了用户错误的建议!
+第三个问题是如果打开文件失败我们使用 `expect` 来打印出错误信息,不过这个错误信息只是说 `file not found`。除了缺少文件之外还有很多可以导致打开文件失败的方式:例如,文件可能存在,不过可能没有打开它的权限。如果我们现在就出于这种情况,打印出的 `file not found` 错误信息就给了用户错误的建议!
-第四,我们不停的使用 `expect` 来处理不同的错误,如果用户没有指定足够的参数来运行程序,他们会从 Rust 得到 "index out of bounds" 错误,而这并不能明确的解释问题。如果所有的错误处理都位于一处这样将来的维护者在需要修改错误处理逻辑时就只需要考虑这一处代码。将所有的错误处理都放在一处也有助于确保我们打印的错误信息对终端用户来说是有意义的。
+第四,我们不停的使用 `expect` 来处理不同的错误,如果用户没有指定足够的参数来运行程序,他们会从 Rust 得到 `index out of bounds` 错误,而这并不能明确的解释问题。如果所有的错误处理都位于一处这样将来的维护者在需要修改错误处理逻辑时就只需要考虑这一处代码。将所有的错误处理都放在一处也有助于确保我们打印的错误信息对终端用户来说是有意义的。
让我们通过重构项目来解决这些问题。
@@ -24,6 +24,7 @@
2. 当命令行解析逻辑比较小时,可以保留在 *main.rs* 中。
3. 当命令行解析开始变得复杂时,也同样将其从 *main.rs* 提取到 *lib.rs* 中。
4. 经过这些过程之后保留在 `main` 函数中的责任应该被限制为:
+
* 使用参数值调用命令行解析逻辑
* 设置任何其他的配置
* 调用 *lib.rs* 中的 `run` 函数
@@ -74,7 +75,7 @@ fn parse_config(args: &[String]) -> (&str, &str) {
```rust,should_panic
# use std::env;
-# use std::fs::File;
+# use std::fs;
#
fn main() {
let args: Vec = env::args().collect();
@@ -84,7 +85,8 @@ fn main() {
println!("Searching for {}", config.query);
println!("In file {}", config.filename);
- let mut f = File::open(config.filename).expect("file not found");
+ let contents = fs::read_to_string(config.filename)
+ .expect("Something went wrong reading the file");
// --snip--
}
@@ -104,7 +106,7 @@ fn parse_config(args: &[String]) -> Config {
示例 12-6:重构 `parse_config` 返回一个 `Config` 结构体实例
-`parse_config` 的签名表明它现在返回一个 `Config` 值。在 `parse_config` 的函数体中,之前返回引用了 `args` 中 `String` 值的字符串 slice,现在我们选择定义 `Config` 来包含拥有所有权的 `String` 值。`main` 中的 `args` 变量是参数值的所有者并只允许 `parse_config` 函数借用他们,这意味着如果 `Config` 尝试获取 `args` 中值的所有权将违反 Rust 的借用规则。
+`parse_config` 的签名表明它现在返回一个 `Config` 值。在之前的 `parse_config` 函数体中,我们返回了引用 `args` 中 `String` 值的字符串 slice,现在我们定义 `Config` 来包含拥有所有权的 `String` 值。`main` 中的 `args` 变量是参数值的所有者并只允许 `parse_config` 函数借用他们,这意味着如果 `Config` 尝试获取 `args` 中值的所有权将违反 Rust 的借用规则。
还有许多不同的方式可以处理 `String` 的数据,而最简单但有些不太高效的方式是调用这些值的 `clone` 方法。这会生成 `Config` 实例可以拥有的数据的完整拷贝,不过会比储存字符串数据的引用消耗更多的时间和内存。不过拷贝数据使得代码显得更加直白因为无需管理引用的生命周期,所以在这种情况下牺牲一小部分性能来换取简洁性的取舍是值得的。
@@ -174,7 +176,7 @@ note: Run with `RUST_BACKTRACE=1` for a backtrace.
#### 改善错误信息
-在示例 12-8 中,在 `new` 函数中增加了一个检查在访问索引 1 和 2 之前检查 slice 是否足够长。如果 slice 不够长,我们使用一个更好的错误信息 panic 而不是 `index out of bounds` 信息:
+在示例 12-8 中,在 `new` 函数中增加了一个检查在访问索引 `1` 和 `2` 之前检查 slice 是否足够长。如果 slice 不够长,我们使用一个更好的错误信息 panic 而不是 `index out of bounds` 信息:
文件名: src/main.rs
@@ -189,7 +191,7 @@ fn new(args: &[String]) -> Config {
示例 12-8:增加一个参数数量检查
-这类似于示例 9-9 中的 `Guess::new` 函数,那里如果 `value` 参数超出了有效值的范围就调用 `panic!`。不同于检查值的范围,这里检查 `args` 的长度至少是 3,而函数的剩余部分则可以在假设这个条件成立的基础上运行。如果
+这类似于示例 9-9 中的 `Guess::new` 函数,那里如果 `value` 参数超出了有效值的范围就调用 `panic!`。不同于检查值的范围,这里检查 `args` 的长度至少是 `3`,而函数的剩余部分则可以在假设这个条件成立的基础上运行。如果
`args` 少于 3 个项,则这个条件将为真,并调用 `panic!` 立即终止程序。
有了 `new` 中这几行额外的代码,再次不带任何参数运行程序并看看现在错误看起来像什么:
@@ -203,13 +205,13 @@ thread 'main' panicked at 'not enough arguments', src/main.rs:30:12
note: Run with `RUST_BACKTRACE=1` for a backtrace.
```
-这个输出就好多了,现在有了一个合理的错误信息。然而,还是有一堆额外的信息我们不希望提供给用户。所以在这里使用示例 9-9 中的技术可能不是最好的;正如第九章所讲到的一样,`panic!` 的调用更趋向于程序上的问题而不是使用上的问题。相反我们可以使用第九章学习的另一个技术:返回一个可以表明成功或错误的 `Result`。
+这个输出就好多了,现在有了一个合理的错误信息。然而,还是有一堆额外的信息我们不希望提供给用户。所以在这里使用示例 9-9 中的技术可能不是最好的;正如第九章所讲到的一样,`panic!` 的调用更趋向于程序上的问题而不是使用上的问题。相反我们可以使用第九章学习的另一个技术 —— 返回一个可以表明成功或错误的 `Result`。
#### 从 `new` 中返回 `Result` 而不是调用 `panic!`
我们可以选择返回一个 `Result` 值,它在成功时会包含一个 `Config` 的实例,而在错误时会描述问题。当 `Config::new` 与 `main` 交流时,可以使用 `Result` 类型来表明这里存在问题。接着修改 `main` 将 `Err` 成员转换为对用户更友好的错误,而不是 `panic!` 调用产生的关于 `thread 'main'` 和 `RUST_BACKTRACE` 的文本。
-示例 12-9 展示了为了返回 `Result` 在 `Config::new` 的返回值和函数体中所需的改变:
+示例 12-9 展示了为了返回 `Result` 在 `Config::new` 的返回值和函数体中所需的改变。注意这还不能编译,直到下一个示例同时也更新了 `main` 之后。
文件名: src/main.rs
@@ -291,10 +293,7 @@ fn main() {
}
fn run(config: Config) {
- let mut f = File::open(config.filename).expect("file not found");
-
- let mut contents = String::new();
- f.read_to_string(&mut contents)
+ let contents = fs::read_to_string(config.filename)
.expect("something went wrong reading the file");
println!("With text:\n{}", contents);
@@ -318,11 +317,8 @@ use std::error::Error;
// --snip--
-fn run(config: Config) -> Result<(), Box> {
- let mut f = File::open(config.filename)?;
-
- let mut contents = String::new();
- f.read_to_string(&mut contents)?;
+fn run(config: Config) -> Result<(), Box> {
+ let contents = fs::read_to_string(config.filename)?;
println!("With text:\n{}", contents);
@@ -334,9 +330,9 @@ fn run(config: Config) -> Result<(), Box> {
这里我们做出了三个明显的修改。首先,将 `run` 函数的返回类型变为 `Result<(), Box>`。之前这个函数返回 unit 类型 `()`,现在它仍然保持作为 `Ok` 时的返回值。
-对于错误类型,使用了 **trait 对象** `Box`(在开头使用了 `use` 语句将 `std::error::Error` 引入作用域)。第十七章会涉及 trait 对象。目前只需知道 `Box` 意味着函数会返回实现了 `Error` trait 的类型,不过无需指定具体将会返回的值的类型。这提供了在不同的错误场景可能有不同类型的错误返回值的灵活性。
+对于错误类型,使用了 **trait 对象** `Box`(在开头使用了 `use` 语句将 `std::error::Error` 引入作用域)。第十七章会涉及 trait 对象。目前只需知道 `Box` 意味着函数会返回实现了 `Error` trait 的类型,不过无需指定具体将会返回的值的类型。这提供了在不同的错误场景可能有不同类型的错误返回值的灵活性。这也就是 `dyn`,它是 “动态的”(“dynamic”)的缩写。
-第二个改变是去掉了 `expect` 调用并替换为第九章讲到的 `?`。不同于遇到错误就 `panic!`,这会从函数中返回错误值并让调用者来处理它。
+第二个改变是去掉了 `expect` 调用并替换为第九章讲到的 `?`。不同于遇到错误就 `panic!`,`?` 会从函数中返回错误值并让调用者来处理它。
第三个修改是现在成功时这个函数会返回一个 `Ok` 值。因为 `run` 函数签名中声明成功类型返回值是 `()`,这意味着需要将 unit 类型值包装进 `Ok` 值中。`Ok(())` 一开始看起来有点奇怪,不过这样使用 `()` 是表明我们调用 `run` 只是为了它的副作用的惯用方式;它并没有返回什么有意义的值。
@@ -351,7 +347,7 @@ warning: unused `std::result::Result` which must be used
= note: #[warn(unused_must_use)] on by default
```
-Rust 提示我们的代码忽略了 `Result` 值,它可能表明这里存在一个错误。虽然我们没有检查这里是否有一个错误,而编译器提醒我们这里应该有一些错误处理代码!现在就让我们修正他们。
+Rust 提示我们的代码忽略了 `Result` 值,它可能表明这里存在一个错误。虽然我们没有检查这里是否有一个错误,而编译器提醒我们这里应该有一些错误处理代码!现在就让我们修正这个问题。
#### 处理 `main` 中 `run` 返回的错误
@@ -395,8 +391,7 @@ fn main() {
```rust,ignore
use std::error::Error;
-use std::fs::File;
-use std::io::prelude::*;
+use std::fs;
pub struct Config {
pub query: String,
@@ -409,25 +404,24 @@ impl Config {
}
}
-pub fn run(config: Config) -> Result<(), Box> {
+pub fn run(config: Config) -> Result<(), Box> {
// --snip--
}
```
示例 12-13:将 `Config` 和 `run` 移动到 *src/lib.rs*
-这里使用了公有的 `pub`:在 `Config`、其字段和其 `new` 方法,以及 `run` 函数上。现在我们有了一个拥有可以测试的公有 API 的库 crate 了。
+这里使用了公有的 `pub` 关键字:在 `Config`、其字段和其 `new` 方法,以及 `run` 函数上。现在我们有了一个拥有可以测试的公有 API 的库 crate 了。
现在需要在 *src/main.rs* 中将移动到 *src/lib.rs* 的代码引入二进制 crate 的作用域中,如示例 12-14 所示:
Filename: src/main.rs
```rust,ignore
-extern crate minigrep;
-
use std::env;
use std::process;
+use minigrep;
use minigrep::Config;
fn main() {
@@ -440,8 +434,8 @@ fn main() {
示例 12-14:将 `minigrep` crate 引入 *src/main.rs* 的作用域中
-为了将库 crate 引入二进制 crate,我们使用 `extern crate minigrep`。接着增加 `use minigrep::Config` 将 `Config` 类型引入作用域,并使用 crate 名作为 `run` 函数的前缀。通过这些重构,所有功能应该能够联系在一起并运行了。运行 `cargo run` 来确保一切都正确的衔接在一起。
+为了将库 crate 引入二进制 crate,我们使用了 `use minigrep`。接着 `use minigrep::Config` 将 `Config` 类型引入作用域,并使用 crate 名称作为 `run` 函数的前缀。通过这些重构,所有功能应该能够联系在一起并运行了。运行 `cargo run` 来确保一切都正确的衔接在一起。
哇哦!这可有很多的工作,不过我们为将来的成功打下了基础。现在处理错误将更容易,同时代码也更加模块化。从现在开始几乎所有的工作都将在 *src/lib.rs* 中进行。
-让我们利用这些新创建的模块的优势来进行一些在旧代码中难以展开的工作,他们在新代码中却很简单:编写测试!
+让我们利用这些新创建的模块的优势来进行一些在旧代码中难以展开的工作,这些工作在新代码中非常容易实现,那就是:编写测试!
diff --git a/src/ch12-04-testing-the-librarys-functionality.md b/src/ch12-04-testing-the-librarys-functionality.md
index 903aaf5..62737be 100644
--- a/src/ch12-04-testing-the-librarys-functionality.md
+++ b/src/ch12-04-testing-the-librarys-functionality.md
@@ -1,8 +1,8 @@
-## 采用测试驱动开发完善库的功能
+## 采用测试驱动开发完善库的功能
-> [ch12-04-testing-the-librarys-functionality.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch12-04-testing-the-librarys-functionality.md)
+> [ch12-04-testing-the-librarys-functionality.md](https://github.com/rust-lang/book/blob/master/src/ch12-04-testing-the-librarys-functionality.md)
>
-> commit 1fe78a83f37ecc69b840fdc8dcfc727f88a3a3d4
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
现在我们将逻辑提取到了 *src/lib.rs* 并将所有的参数解析和错误处理留在了 *src/main.rs* 中,为代码的核心功能编写测试将更加容易。我们可以直接使用多种参数调用函数并检查返回值而无需从命令行运行二进制文件了。如果你愿意的话,请自行为 `Config::new` 和 `run` 函数的功能编写一些测试。
@@ -29,7 +29,7 @@
# }
#
#[cfg(test)]
-mod test {
+mod tests {
use super::*;
#[test]
@@ -50,14 +50,14 @@ Pick three.";
示例 12-15:创建一个我们期望的 `search` 函数的失败测试
-这里选择使用 "duct" 作为这个测试中需要搜索的字符串。用来搜索的文本有三行,其中只有一行包含 "duct"。我们断言 `search` 函数的返回值只包含期望的那一行。
+这里选择使用 `"duct"` 作为这个测试中需要搜索的字符串。用来搜索的文本有三行,其中只有一行包含 `"duct"`。我们断言 `search` 函数的返回值只包含期望的那一行。
-我们还不能运行这个测试并看到它失败,因为它甚至都还不能编译!我们将增加足够的代码来使其能够编译:一个总是会返回空 vector 的 `search` 函数定义,如示例 12-16 所示。然后这个测试应该能够编译并因为空 vector 并不匹配一个包含一行 `"safe, fast, productive."` 的 vector 而失败。
+我们还不能运行这个测试并看到它失败,因为它甚至都还不能编译:`search` 函数还不存在呢!我们将增加足够的代码来使其能够编译:一个总是会返回空 vector 的 `search` 函数定义,如示例 12-16 所示。然后这个测试应该能够编译并因为空 vector 并不匹配一个包含一行 `"safe, fast, productive."` 的 vector 而失败。
文件名: src/lib.rs
```rust
-pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
+fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
vec![]
}
```
@@ -74,7 +74,7 @@ pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
error[E0106]: missing lifetime specifier
--> src/lib.rs:5:51
|
-5 | pub fn search(query: &str, contents: &str) -> Vec<&str> {
+5 | fn search(query: &str, contents: &str) -> Vec<&str> {
| ^ expected lifetime
parameter
|
@@ -84,7 +84,7 @@ parameter
Rust 不可能知道我们需要的是哪一个参数,所以需要告诉它。因为参数 `contents` 包含了所有的文本而且我们希望返回匹配的那部分文本,所以我们知道 `contents` 是应该要使用生命周期语法来与返回值相关联的参数。
-其他语言中并不需要你在函数签名中将参数与返回值相关联,所以这么做可能仍然感觉有些陌生,随着时间的推移这将会变得越来越容易。你可能想要将这个例子与第十章中生命周期语法部分做对比。
+其他语言中并不需要你在函数签名中将参数与返回值相关联。所以这么做可能仍然感觉有些陌生,随着时间的推移这将会变得越来越容易。你可能想要将这个例子与第十章中生命 “生命周期与引用有效性” 部分做对比。
现在运行测试:
@@ -96,12 +96,12 @@ $ cargo test
Running target/debug/deps/minigrep-abcabcabc
running 1 test
-test test::one_result ... FAILED
+test tests::one_result ... FAILED
failures:
----- test::one_result stdout ----
- thread 'test::one_result' panicked at 'assertion failed: `(left ==
+---- tests::one_result stdout ----
+ thread 'tests::one_result' panicked at 'assertion failed: `(left ==
right)`
left: `["safe, fast, productive."]`,
right: `[]`)', src/lib.rs:48:8
@@ -109,7 +109,7 @@ note: Run with `RUST_BACKTRACE=1` for a backtrace.
failures:
- test::one_result
+ tests::one_result
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out
@@ -137,7 +137,7 @@ Rust 有一个有助于一行一行遍历字符串的方法,出于方便它被
文件名: src/lib.rs
```rust,ignore
-pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
+fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
for line in contents.lines() {
// do something with line
}
@@ -146,7 +146,7 @@ pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
示例 12-17:遍历 `contents` 的每一行
-`lines` 方法返回一个迭代器。第十三章会深入了解迭代器,不过我们已经在示例 3-4 中见过使用迭代器的方法了,在那里使用了一个 `for` 循环和迭代器在一个集合的每一项上运行了一些代码。
+`lines` 方法返回一个迭代器。第十三章会深入了解迭代器,不过我们已经在示例 3-5 中见过使用迭代器的方法了,在那里使用了一个 `for` 循环和迭代器在一个集合的每一项上运行了一些代码。
#### 用查询字符串搜索每一行
@@ -155,7 +155,7 @@ pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
文件名: src/lib.rs
```rust,ignore
-pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
+fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
for line in contents.lines() {
if line.contains(query) {
// do something with line
@@ -173,7 +173,7 @@ pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
文件名: src/lib.rs
```rust,ignore
-pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
+fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
let mut results = Vec::new();
for line in contents.lines() {
@@ -194,7 +194,7 @@ pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
$ cargo test
--snip--
running 1 test
-test test::one_result ... ok
+test tests::one_result ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
```
@@ -210,11 +210,8 @@ test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
文件名: src/lib.rs
```rust,ignore
-pub fn run(config: Config) -> Result<(), Box> {
- let mut f = File::open(config.filename)?;
-
- let mut contents = String::new();
- f.read_to_string(&mut contents)?;
+pub fn run(config: Config) -> Result<(), Box> {
+ let contents = fs::read_to_string(config.filename)?;
for line in search(&config.query, &contents) {
println!("{}", line);
diff --git a/src/ch12-05-working-with-environment-variables.md b/src/ch12-05-working-with-environment-variables.md
index a61ef43..b90174e 100644
--- a/src/ch12-05-working-with-environment-variables.md
+++ b/src/ch12-05-working-with-environment-variables.md
@@ -1,10 +1,10 @@
-## 处理环境变量
+## 处理环境变量
-> [ch12-05-working-with-environment-variables.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch12-05-working-with-environment-variables.md)
+> [ch12-05-working-with-environment-variables.md](https://github.com/rust-lang/book/blob/master/src/ch12-05-working-with-environment-variables.md)
>
-> commit 1fe78a83f37ecc69b840fdc8dcfc727f88a3a3d4
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
-我们将增加一个额外的功能来改进 `minigrep`:一个通过环境变量启用的大小写不敏感搜索的选项。可以将其设计为一个命令行参数并要求用户每次需要时都加上它,不过相反我们将使用环境变量。这允许用户设置环境变量一次之后在整个终端会话中所有的搜索都将是大小写不敏感的。
+我们将增加一个额外的功能来改进 `minigrep`:用户可以通过设置环境变量来设置搜索是否是大小写敏感的 。当然,我们也可以将其设计为一个命令行参数并要求用户每次需要时都加上它,不过在这里我们将使用环境变量。这允许用户设置环境变量一次之后在整个终端会话中所有的搜索都将是大小写不敏感的。
### 编写一个大小写不敏感 `search` 函数的失败测试
@@ -14,7 +14,7 @@
```rust
#[cfg(test)]
-mod test {
+mod tests {
use super::*;
#[test]
@@ -51,9 +51,9 @@ Trust me.";
示例 12-20:为准备添加的大小写不敏感函数新增失败测试
-注意我们也改变了老测试中 `contents` 的值。还新增了一个含有文本 "Duct tape" 的行,它有一个大写的 D,这在大小写敏感搜索时不应该匹配 "duct"。我们修改这个测试以确保不会意外破坏已经实现的大小写敏感搜索功能;这个测试现在应该能通过并在处理大小写不敏感搜索时应该能一直通过。
+注意我们也改变了老测试中 `contents` 的值。还新增了一个含有文本 `"Duct tape."` 的行,它有一个大写的 D,这在大小写敏感搜索时不应该匹配 "duct"。我们修改这个测试以确保不会意外破坏已经实现的大小写敏感搜索功能;这个测试现在应该能通过并在处理大小写不敏感搜索时应该能一直通过。
-大小写 **不敏感** 搜索的新测试使用 "rUsT" 作为其查询字符串。在我们将要增加的 `search_case_insensitive` 函数中,“rUsT” 查询应该包含 “Rust:” 包含一个大写的 R 还有 “Trust me.” 这两行,即便他们与查询的大小写都不同。这个测试现在会编译失败因为还没有定义 `search_case_insensitive` 函数。请随意增加一个总是返回空 vector 的骨架实现,正如示例 12-16 中 `search` 函数为了使测试编译并失败时所做的那样。
+大小写 **不敏感** 搜索的新测试使用 `"rUsT"` 作为其查询字符串。在我们将要增加的 `search_case_insensitive` 函数中,`"rUsT"` 查询应该包含带有一个大写 R 的 `"Rust:"` 还有 `"Trust me."` 这两行,即便他们与查询的大小写都不同。这个测试现在会编译失败因为还没有定义 `search_case_insensitive` 函数。请随意增加一个总是返回空 vector 的骨架实现,正如示例 12-16 中 `search` 函数为了使测试编译并失败时所做的那样。
### 实现 `search_case_insensitive` 函数
@@ -78,9 +78,9 @@ fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
示例 12-21:定义 `search_case_insensitive` 函数,它在比较查询和每一行之前将他们都转换为小写
-首先我们将 `query` 字符串转换为小写,并将其覆盖到同名的变量中。对查询字符串调用 `to_lowercase` 是必需的,这样不管用户的查询是 “rust”、“RUST”、“Rust” 或者 “rUsT”,我们都将其当作 “rust” 处理并对大小写不敏感。
+首先我们将 `query` 字符串转换为小写,并将其覆盖到同名的变量中。对查询字符串调用 `to_lowercase` 是必需的,这样不管用户的查询是 `"rust"`、`"RUST"`、`"Rust"` 或者 `"rUsT"`,我们都将其当作 `"rust"` 处理并对大小写不敏感。
-注意 `query` 现在是一个 `String` 而不是字符串 slice,因为调用 `to_lowercase` 是在创建新数据,而不是引用现有数据。如果查询字符串是 “rUsT”,这个字符串 slice 并不包含可供我们使用的小写的 “u” 或 “t”,所以必需分配一个包含 “rust” 的新 `String`。现在当我们将 `query` 作为一个参数传递给 `contains` 方法时,需要增加一个 & 因为 `contains` 的签名被定义为获取一个字符串 slice。
+注意 `query` 现在是一个 `String` 而不是字符串 slice,因为调用 `to_lowercase` 是在创建新数据,而不是引用现有数据。如果查询字符串是 `"rUsT"`,这个字符串 slice 并不包含可供我们使用的小写的 `u` 或 `t`,所以必需分配一个包含 `"rust"` 的新 `String`。现在当我们将 `query` 作为一个参数传递给 `contains` 方法时,需要增加一个 & 因为 `contains` 的签名被定义为获取一个字符串 slice。
接下来在检查每个 `line` 是否包含 `search` 之前增加了一个 `to_lowercase` 调用将他们都变为小写。现在我们将 `line` 和 `query` 都转换成了小写,这样就可以不管查询的大小写进行匹配了。
@@ -88,13 +88,13 @@ fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
```text
running 2 tests
-test test::case_insensitive ... ok
-test test::case_sensitive ... ok
+test tests::case_insensitive ... ok
+test tests::case_sensitive ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
```
-好的!现在,让我们在 `run` 函数中实际调用新 `search_case_insensitive` 函数。首先,我们将在 `Config` 结构体中增加一个配置项来切换大小写敏感和大小写不敏感搜索:
+好的!现在,让我们在 `run` 函数中实际调用新 `search_case_insensitive` 函数。首先,我们将在 `Config` 结构体中增加一个配置项来切换大小写敏感和大小写不敏感搜索。增加这些字段会导致编译错误,因为我们还没有在任何地方初始化这些字段:
文件名: src/lib.rs
@@ -112,7 +112,7 @@ pub struct Config {
```rust
# use std::error::Error;
-# use std::fs::File;
+# use std::fs::{self, File};
# use std::io::prelude::*;
#
# fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
@@ -123,17 +123,14 @@ pub struct Config {
# vec![]
# }
#
-# struct Config {
+# pub struct Config {
# query: String,
# filename: String,
# case_sensitive: bool,
# }
#
-pub fn run(config: Config) -> Result<(), Box> {
- let mut f = File::open(config.filename)?;
-
- let mut contents = String::new();
- f.read_to_string(&mut contents)?;
+pub fn run(config: Config) -> Result<(), Box> {
+ let contents = fs::read_to_string(config.filename)?;
let results = if config.case_sensitive {
search(&config.query, &contents)
@@ -189,7 +186,7 @@ impl Config {
我们将变量 `case_sensitive` 的值传递给 `Config` 实例,这样 `run` 函数可以读取其值并决定是否调用 `search` 或者示例 12-22 中实现的 `search_case_insensitive`。
-让我们试一试吧!首先不设置环境变量并使用查询 “to” 运行程序,这应该会匹配任何全小写的单词 “to” 的行:
+让我们试一试吧!首先不设置环境变量并使用查询 `to` 运行程序,这应该会匹配任何全小写的单词 “to” 的行:
```text
$ cargo run to poem.txt
@@ -200,7 +197,16 @@ Are you nobody, too?
How dreary to be somebody!
```
-看起来程序仍然能够工作!现在将 `CASE_INSENSITIVE` 设置为 1 并仍使用相同的查询 “to”,这回应该得到包含可能有大写字母的 “to” 的行:
+看起来程序仍然能够工作!现在将 `CASE_INSENSITIVE` 设置为 `1` 并仍使用相同的查询 `to`。
+
+如果你使用 PowerShell,则需要用两个命令来设置环境变量并运行程序:
+
+```text
+$ $env:CASE_INSENSITIVE=1
+$ cargo run to poem.txt
+```
+
+这回应该得到包含可能有大写字母的 “to” 的行:
```text
$ CASE_INSENSITIVE=1 cargo run to poem.txt
@@ -212,15 +218,8 @@ To tell your name the livelong day
To an admiring bog!
```
-如果你使用 PowerShell,则需要用两句命令而不是一句来设置环境变量并运行程序:
-
-```text
-$ $env.CASE_INSENSITIVE=1
-$ cargo run to poem.txt
-```
-
好极了,我们也得到了包含 “To” 的行!现在 `minigrep` 程序可以通过环境变量控制进行大小写不敏感搜索了。现在你知道了如何管理由命令行参数或环境变量设置的选项了!
一些程序允许对相同配置同时使用参数 **和** 环境变量。在这种情况下,程序来决定参数和环境变量的优先级。作为一个留给你的测试,尝试通过一个命令行参数或一个环境变量来控制大小写不敏感搜索。并在运行程序时遇到矛盾值时决定命令行参数和环境变量的优先级。
-`std::env` 模块还包含了更多处理环境变量的实用功能;请查看官方文档来了解其可用的功能。
\ No newline at end of file
+`std::env` 模块还包含了更多处理环境变量的实用功能;请查看官方文档来了解其可用的功能。
diff --git a/src/ch12-06-writing-to-stderr-instead-of-stdout.md b/src/ch12-06-writing-to-stderr-instead-of-stdout.md
index 3decb28..53ea29d 100644
--- a/src/ch12-06-writing-to-stderr-instead-of-stdout.md
+++ b/src/ch12-06-writing-to-stderr-instead-of-stdout.md
@@ -1,8 +1,8 @@
-## 将错误信息输出到标准错误而不是标准输出
+## 将错误信息输出到标准错误而不是标准输出
-> [ch12-06-writing-to-stderr-instead-of-stdout.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch12-06-writing-to-stderr-instead-of-stdout.md)
+> [ch12-06-writing-to-stderr-instead-of-stdout.md](https://github.com/rust-lang/book/blob/master/src/ch12-06-writing-to-stderr-instead-of-stdout.md)
>
-> commit 1fe78a83f37ecc69b840fdc8dcfc727f88a3a3d4
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
目前为止,我们将所有的输出都 `println!` 到了终端。大部分终端都提供了两种输出:**标准输出**(*standard output*,`stdout`)对应通用信息,**标准错误**(*standard error*,`stderr`)则用于错误信息。这种区别允许用户选择将程序正常输出定向到一个文件中并仍将错误信息打印到屏幕上。
@@ -10,11 +10,11 @@
### 检查错误应该写入何处
-首先,让我们观察一下目前 `minigrep` 打印的所有内容都被写入了标准输出,包括应该被写入标准错误的错误信息。可以通过将标准输出流重定向到一个文件同时有意产生一个错误来做到这一点。我们没有重定向标准错误流,所以任何发送到标准错误的内容将会继续显示在屏幕上。
+首先,让我们观察一下目前 `minigrep` 打印的所有内容是如何被写入标准输出的,包括那些应该被写入标准错误的错误信息。可以通过将标准输出流重定向到一个文件同时有意产生一个错误来做到这一点。我们没有重定向标准错误流,所以任何发送到标准错误的内容将会继续显示在屏幕上。
命令行程序被期望将错误信息发送到标准错误流,这样即便选择将标准输出流重定向到文件中时仍然能看到错误信息。目前我们的程序并不符合期望;相反我们将看到它将错误信息输出保存到了文件中。
-展示这种行为的方式是通过 `>` 和文件名 *output.txt* 来与运行程序,这个文件是期望重定向标准输出流的位置。并不传递任何参数应该会产生一个错误:
+我们通过 `>` 和文件名 *output.txt* 来运行程序,我们期望重定向标准输出流到该文件中。在这里,我们没有传递任何参数,所以会产生一个错误:
```text
$ cargo run > output.txt
@@ -26,7 +26,7 @@ $ cargo run > output.txt
Problem parsing arguments: not enough arguments
```
-是的,错误信息被打印到了标准输出中。像这样的错误信息被打印到标准错误中将有用的多,并在重定向标准输出时只将成功运行的信息写入文件。我们将改变他们。
+是的,错误信息被打印到了标准输出中。像这样的错误信息被打印到标准错误中将会有用的多,并且只将成功运行的信息写入文件。接下来我们将对程序进行修改从而以这种方式进行输出。
### 将错误打印到标准错误
@@ -60,7 +60,7 @@ $ cargo run > output.txt
Problem parsing arguments: not enough arguments
```
-现在我们看到了屏幕上的错误信息,同时 `output.txt` 里什么也没有,这正是命令行程序所期望的行为。
+现在我们看到了屏幕上的错误信息,同时 *output.txt* 里什么也没有,这正是命令行程序所期望的行为。
如果使用不会造成错误的参数再次运行程序,不过仍然将标准输出重定向到一个文件,像这样:
diff --git a/src/ch13-00-functional-features.md b/src/ch13-00-functional-features.md
index 6de9f6e..e9ae891 100644
--- a/src/ch13-00-functional-features.md
+++ b/src/ch13-00-functional-features.md
@@ -1,16 +1,18 @@
# Rust 中的函数式语言功能:迭代器与闭包
-> [ch13-00-functional-features.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch13-00-functional-features.md)
+> [ch13-00-functional-features.md](https://github.com/rust-lang/book/blob/master/src/ch13-00-functional-features.md)
>
-> commit 2bcb126815a381acc3d46b0d6fc382cb4c98fbc5
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
-Rust 的设计灵感来源于很多现存的语言和技术。其中一个显著的影响就是 **函数式编程**(*functional programming*)。函数式编程风格通常包含将函数作为参数值或其他函数的返回值、将函数赋值给变量以供之后执行等等。我们不会在这里讨论函数式编程是或不是什么的问题,而是展示 Rust 的一些在功能上与其他被认为是函数式语言类似的特性。
+Rust 的设计灵感来源于很多现存的语言和技术。其中一个显著的影响就是 **函数式编程**(*functional programming*)。函数式编程风格通常包含将函数作为参数值或其他函数的返回值、将函数赋值给变量以供之后执行等等。
+
+本章我们不会讨论函数式编程是或不是什么的问题,而是展示 Rust 的一些在功能上与其他被认为是函数式语言类似的特性。
更具体的,我们将要涉及:
* **闭包**(*Closures*),一个可以储存在变量里的类似函数的结构
* **迭代器**(*Iterators*),一种处理元素序列的方式
* 如何使用这些功能来改进第十二章的 I/O 项目。
-* 这两个功能的性能。(**剧透高能:** 他们的速度超乎你的想象!)
+* 这两个功能的性能。(**剧透警告:** 他们的速度超乎你的想象!)
还有其它受函数式风格影响的 Rust 功能,比如模式匹配和枚举,这些已经在其他章节中讲到过了。掌握闭包和迭代器则是编写符合语言风格的高性能 Rust 代码的重要一环,所以我们将专门用一整章来讲解他们。
diff --git a/src/ch13-01-closures.md b/src/ch13-01-closures.md
index 712c300..278f942 100644
--- a/src/ch13-01-closures.md
+++ b/src/ch13-01-closures.md
@@ -1,8 +1,8 @@
## 闭包:可以捕获环境的匿名函数
-> [ch13-01-closures.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch13-01-closures.md)
+> [ch13-01-closures.md](https://github.com/rust-lang/book/blob/master/src/ch13-01-closures.md)
>
-> commit bdcba470e4a71d16242b1727bbf99b300c194c46
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
Rust 的 **闭包**(*closures*)是可以保存进变量或作为参数传递给其他函数的匿名函数。可以在一个地方创建闭包,然后在不同的上下文中执行闭包运算。不同于函数,闭包允许捕获调用者作用域中的值。我们将展示闭包的这些功能如何复用代码和自定义行为。
@@ -27,14 +27,14 @@ fn simulated_expensive_calculation(intensity: u32) -> u32 {
}
```
-示例 13-1:一个用来代替假象计算的函数,它大约会执行两秒钟
+示例 13-1:一个用来代替假想计算的函数,它大约会执行两秒钟
接下来,`main` 函数中将会包含本例的健身 app 中的重要部分。这代表当用户请求健身计划时 app 会调用的代码。因为与 app 前端的交互与闭包的使用并不相关,所以我们将硬编码代表程序输入的值并打印输出。
所需的输入有这些:
-* **一个来自用户的 intensity 数字**,请求健身计划时指定,它代表用户喜好低强度还是高强度健身。
-* **一个随机数**,其会在健身计划中生成变化。
+* 一个来自用户的 intensity 数字,请求健身计划时指定,它代表用户喜好低强度还是高强度健身。
+* 一个随机数,其会在健身计划中生成变化。
程序的输出将会是建议的锻炼计划。示例 13-2 展示了我们将要使用的 `main` 函数:
@@ -55,7 +55,7 @@ fn main() {
示例 13-2:`main` 函数包含了用于 `generate_workout` 函数的模拟用户输入和模拟随机数输入
-处于简单考虑这里硬编码了 `simulated_user_specified_value` 变量的值为 10 和 `simulated_random_number` 变量的值为 7;一个实际的程序会从 app 前端获取强度系数并使用 `rand` crate 来生成随机数,正如第二章的猜猜看游戏所做的那样。`main` 函数使用模拟的输入值调用 `generate_workout` 函数:
+出于简单考虑这里硬编码了 `simulated_user_specified_value` 变量的值为 10 和 `simulated_random_number` 变量的值为 7;一个实际的程序会从 app 前端获取强度系数并使用 `rand` crate 来生成随机数,正如第二章的猜猜看游戏所做的那样。`main` 函数使用模拟的输入值调用 `generate_workout` 函数:
现在有了执行上下文,让我们编写算法。示例 13-3 中的 `generate_workout` 函数包含本例中我们最关心的 app 业务逻辑。本例中余下的代码修改都将在这个函数中进行:
@@ -96,7 +96,7 @@ fn generate_workout(intensity: u32, random_number: u32) {
示例 13-3:程序的业务逻辑,它根据输入并调用 `simulated_expensive_calculation` 函数来打印出健身计划
-示例 13-3 中的代码有多处慢计算函数的调用。第一个 `if` 块调用了 `simulated_expensive_calculation` 两次,外部 `else` 中的 `if` 完全没有调用它,第二个 `else` 中的代码调用了它一次。
+示例 13-3 中的代码有多处调用了慢计算函数 `simulated_expensive_calculation` 。第一个 `if` 块调用了 `simulated_expensive_calculation` 两次, `else` 中的 `if` 没有调用它,而第二个 `else` 中的代码调用了它一次。
@@ -178,7 +178,7 @@ let expensive_closure = |num| {
闭包定义是 `expensive_closure` 赋值的 `=` 之后的部分。闭包的定义以一对竖线(`|`)开始,在竖线中指定闭包的参数;之所以选择这个语法是因为它与 Smalltalk 和 Ruby 的闭包定义类似。这个闭包有一个参数 `num`;如果有多于一个参数,可以使用逗号分隔,比如 `|param1, param2|`。
-参数之后是存放闭包体的大括号 ———— 如果闭包体只有一行则大括号是可以省略的。大括号之后闭包的结尾,需要用于 `let` 语句的分号。闭包体的最后一行(`num`)返回的值将是调用闭包时返回的值,因为最后一行没有分号;正如函数体中的一样。
+参数之后是存放闭包体的大括号 —— 如果闭包体只有一行则大括号是可以省略的。大括号之后闭包的结尾,需要用于 `let` 语句的分号。因为闭包体的最后一行没有分号(正如函数体一样),所以其返回了值 `num` 。
注意这个 `let` 语句意味着 `expensive_closure` 包含一个匿名函数的 **定义**,不是调用匿名函数的 **返回值**。回忆一下使用闭包的原因是我们需要在一个位置定义代码,储存代码,并在之后的位置实际调用它;期望调用的代码现在储存在 `expensive_closure` 中。
@@ -223,7 +223,7 @@ fn generate_workout(intensity: u32, random_number: u32) {
现在耗时的计算只在一个地方被调用,并只会在需要结果的时候执行改代码。
-然而,我们又重新引入了示例 13-3 中的问题:仍然在第一个 `if` 块中调用了闭包两次,这会调用慢计算两次并使用户多等待一倍的时间。可以通过在 `if` 块中创建一个本地变量存放闭包调用的结果来解决这个问题,不过正因为使用了闭包还有另一个解决方案。稍后会回到这个方案上;首先讨论一下为何闭包定义中和所涉及的 trait 中没有类型注解。
+然而,我们又重新引入了示例 13-3 中的问题:仍然在第一个 `if` 块中调用了闭包两次,这调用了慢计算代码两次而使得用户需要多等待一倍的时间。可以通过在 `if` 块中创建一个本地变量存放闭包调用的结果来解决这个问题,不过闭包可以提供另外一种解决方案。我们稍后会讨论这个方案,不过目前让我们首先讨论一下为何闭包定义中和所涉及的 trait 中没有类型注解。
### 闭包类型推断和注解
@@ -265,7 +265,7 @@ let add_one_v4 = |x| x + 1 ;
文件名: src/main.rs
-```rust,ignore
+```rust,ignore,does_not_compile
let example_closure = |x| x;
let s = example_closure(String::from("hello"));
@@ -296,9 +296,9 @@ error[E0308]: mismatched types
幸运的是,还有另一个可用的方案。可以创建一个存放闭包和调用闭包结果的结构体。该结构体只会在需要结果时执行闭包,并会缓存结果值,这样余下的代码就不必再负责保存结果并可以复用该值。你可能见过这种模式被称 *memoization* 或 *lazy evaluation*。
-为了让结构体存放闭包,我们需要能够指定闭包的类型,因为结构体定义需要知道其每一个字段的类型。每一个闭包实例有其自己独有的匿名类型:也就是说,即便两个闭包有着相同的签名,他们的类型仍然可以被认为是不同。为了定义使用闭包的结构体、枚举或函数参数,需要像第十章讨论的那样使用泛型和 trait bound。
+为了让结构体存放闭包,我们需要指定闭包的类型,因为结构体定义需要知道其每一个字段的类型。每一个闭包实例有其自己独有的匿名类型:也就是说,即便两个闭包有着相同的签名,他们的类型仍然可以被认为是不同。为了定义使用闭包的结构体、枚举或函数参数,需要像第十章讨论的那样使用泛型和 trait bound。
-`Fn` 系列 trait 由标准库提供。所有的闭包都实现了 trait `Fn`、`FnMut` 或 `FnOnce` 中的一个。在“闭包会捕获其环境”部分捕获环境部分我们会讨论这些 trait 的区别;在这个例子中可以使用 `Fn` trait。
+`Fn` 系列 trait 由标准库提供。所有的闭包都实现了 trait `Fn`、`FnMut` 或 `FnOnce` 中的一个。在“闭包会捕获其环境”部分我们会讨论这些 trait 的区别;在这个例子中可以使用 `Fn` trait。
为了满足 `Fn` trait bound 我们增加了代表闭包所必须的参数和返回值类型的类型。在这个例子中,闭包有一个 `u32` 的参数并返回一个 `u32`,这样所指定的 trait bound 就是 `Fn(u32) -> u32`。
@@ -319,7 +319,7 @@ struct Cacher
结构体 `Cacher` 有一个泛型 `T` 的字段 `calculation`。`T` 的 trait bound 指定了 `T` 是一个使用 `Fn` 的闭包。任何我们希望储存到 `Cacher` 实例的 `calculation` 字段的闭包必须有一个 `u32` 参数(由 `Fn` 之后的括号的内容指定)并必须返回一个 `u32`(由 `->` 之后的内容)。
-> 注意:函数也都实现了这三个 `Fn` trait。如果不需要捕获环境中的值,则在需要实现 `Fn` trait 是可以使用函数而不是闭包。
+> 注意:函数也都实现了这三个 `Fn` trait。如果不需要捕获环境中的值,则可以使用实现了 `Fn` trait 的函数而不是闭包。
`value` 是 `Option` 类型的。在执行闭包之前,`value` 将是 `None`。如果使用 `Cacher` 的代码请求闭包的结果,这时会执行闭包并将结果储存在 `value` 字段的 `Some` 成员中。接着如果代码再次请求闭包的结果,这时不再执行闭包,而是会返回存放在 `Some` 成员中的结果。
@@ -442,11 +442,11 @@ fn generate_workout(intensity: u32, random_number: u32) {
### `Cacher` 实现的限制
-值缓存是一种更加广泛的实用行为,我们可能希望在代码中的其他闭包中也使用他们。然而,目前 `Cacher` 的实现存在一些小问题,这使得在不同上下文中复用变得很困难。
+值缓存是一种更加广泛的实用行为,我们可能希望在代码中的其他闭包中也使用他们。然而,目前 `Cacher` 的实现存在两个小问题,这使得在不同上下文中复用变得很困难。
第一个问题是 `Cacher` 实例假设对于 `value` 方法的任何 `arg` 参数值总是会返回相同的值。也就是说,这个 `Cacher` 的测试会失败:
-```rust,ignore
+```rust,ignore,panics
#[test]
fn call_with_different_values() {
let mut c = Cacher::new(|a| a);
@@ -470,9 +470,9 @@ thread 'call_with_different_values' panicked at 'assertion failed: `(left == rig
这里的问题是第一次使用 1 调用 `c.value`,`Cacher` 实例将 `Some(1)` 保存进 `self.value`。在这之后,无论传递什么值调用 `value`,它总是会返回 1。
-尝试修改 `Cacher` 存放一个哈希 map 而不是单独一个值。哈希 map 的 key 将是传递进来的 `arg` 值,而 value 则是对应 key 调用闭包的结果值。相比之前检查 `self.value` 直接是 `Some` 还是 `None` 值,现在 `value` 会在哈希 map 中寻找 `arg`,如果存在就返回它。如果不存在,`Cacher` 会调用闭包并将结果值保存在哈希 map 对应 `arg` 值的位置。
+尝试修改 `Cacher` 存放一个哈希 map 而不是单独一个值。哈希 map 的 key 将是传递进来的 `arg` 值,而 value 则是对应 key 调用闭包的结果值。相比之前检查 `self.value` 直接是 `Some` 还是 `None` 值,现在 `value` 函数会在哈希 map 中寻找 `arg`,如果找到的话就返回其对应的值。如果不存在,`Cacher` 会调用闭包并将结果值保存在哈希 map 对应 `arg` 值的位置。
-当前 `Cacher` 实现的另一个问题是它的应用被限制为只接受获取一个 `u32` 值并返回一个 `u32` 值的闭包。比如说,我们可能需要能够缓存一个获取字符串 slice 并返回 `usize` 值的闭包的结果。请尝试引入更多泛型参数来增加 `Cacher` 功能的灵活性。
+当前 `Cacher` 实现的第二个问题是它的应用被限制为只接受获取一个 `u32` 值并返回一个 `u32` 值的闭包。比如说,我们可能需要能够缓存一个获取字符串 slice 并返回 `usize` 值的闭包的结果。请尝试引入更多泛型参数来增加 `Cacher` 功能的灵活性。
### 闭包会捕获其环境
@@ -502,7 +502,7 @@ fn main() {
文件名: src/main.rs
-```rust,ignore
+```rust,ignore,does_not_compile
fn main() {
let x = 4;
@@ -527,7 +527,7 @@ error[E0434]: can't capture dynamic environment in a fn item; use the || { ...
编译器甚至会提示我们这只能用于闭包!
-当闭包从环境中捕获一个值,闭包会在闭包体中储存这个值以供使用。这会使用内存并产生额外的开销,当执行不会捕获环境的更通用的代码场景中我们不希望有这些开销。因为函数从未允许捕获环境,定义和使用函数也就从不会有这些额外开销。
+当闭包从环境中捕获一个值,闭包会在闭包体中储存这个值以供使用。这会使用内存并产生额外的开销,在更一般的场景中,当我们不需要闭包来捕获环境时,我们不希望产生这些开销。因为函数从未允许捕获环境,定义和使用函数也就从不会有这些额外开销。
闭包可以通过三种方式捕获其环境,他们直接对应函数的三种获取参数的方式:获取所有权,可变借用和不可变借用。这三种捕获值的方式被编码为如下三个 `Fn` trait:
@@ -543,7 +543,7 @@ error[E0434]: can't capture dynamic environment in a fn item; use the || { ...
文件名: src/main.rs
-```rust,ignore
+```rust,ignore,does_not_compile
fn main() {
let x = vec![1, 2, 3];
@@ -557,7 +557,7 @@ fn main() {
}
```
-这个例子并不能编译:
+这个例子并不能编译,会产生以下错误:
```text
error[E0382]: use of moved value: `x`
@@ -575,6 +575,6 @@ error[E0382]: use of moved value: `x`
`x` 被移动进了闭包,因为闭包使用 `move` 关键字定义。接着闭包获取了 `x` 的所有权,同时 `main` 就不再允许在 `println!` 语句中使用 `x` 了。去掉 `println!` 即可修复问题。
-大部分需要指定一个 `Fn` trait bound 的时候,可以从 `Fn` 开始,而编译器会根据闭包体中的情况告诉你是否需要 `FnMut` 或 `FnOnce`。
+大部分需要指定一个 `Fn` 系列 trait bound 的时候,可以从 `Fn` 开始,而编译器会根据闭包体中的情况告诉你是否需要 `FnMut` 或 `FnOnce`。
-为了展示闭包作为函数参数时捕获其环境的作用,让我们移动到下一个主题:迭代器。
+为了展示闭包作为函数参数时捕获其环境的作用,让我们继续下一个主题:迭代器。
diff --git a/src/ch13-02-iterators.md b/src/ch13-02-iterators.md
index 7f6668c..97f028a 100644
--- a/src/ch13-02-iterators.md
+++ b/src/ch13-02-iterators.md
@@ -1,12 +1,12 @@
## 使用迭代器处理元素序列
-> [ch13-02-iterators.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch13-02-iterators.md)
+> [ch13-02-iterators.md](https://github.com/rust-lang/book/blob/master/src/ch13-02-iterators.md)
>
-> commit ceb31210263d49994bbf09456a35a135da690f24
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
迭代器模式允许你对一个项的序列进行某些处理。**迭代器**(*iterator*)负责遍历序列中的每一项和决定序列何时结束的逻辑。当使用迭代器时,我们无需重新实现这些逻辑。
-在 Rust 中,迭代器是 **惰性的**(*lazy*),这意味着直到调用方法消费迭代器之前它都不会有效果。例如,示例 13-13 中的代码通过调用定义于 `Vec` 上的 `iter` 方法在一个 vector `v1` 上创建了一个迭代器。这段代码本身没有任何用处:
+在 Rust 中,迭代器是**惰性的**(*lazy*),这意味着在调用方法使用迭代器之前它都不会有效果。例如,示例 13-13 中的代码通过调用定义于 `Vec` 上的 `iter` 方法在一个 vector `v1` 上创建了一个迭代器。这段代码本身没有任何用处:
```rust
let v1 = vec![1, 2, 3];
@@ -16,7 +16,7 @@ let v1_iter = v1.iter();
示例 13-13:创建一个迭代器
-创建迭代器之后,可以选择用多种方式利用它。在示例 3-4 中,我们使用迭代器和 `for` 循环在每一个项上执行了一些代码,不过直到现在我们掩盖了 `iter` 调用做了什么。
+一旦创建迭代器之后,可以选择用多种方式利用它。在示例 3-4 中,我们使用迭代器和 `for` 循环在每一个项上执行了一些代码,虽然直到现在为止我们一直没有具体讨论调用 `iter` 到底具体做了什么。
示例 13-14 中的例子将迭代器的创建和 `for` 循环中的使用分开。迭代器被储存在 `v1_iter` 变量中,而这时没有进行迭代。一旦 `for` 循环开始使用 `v1_iter`,接着迭代器中的每一个元素被用于循环的一次迭代,这会打印出其每一个值:
@@ -34,7 +34,7 @@ for val in v1_iter {
在标准库中没有提供迭代器的语言中,我们可能会使用一个从 0 开始的索引变量,使用这个变量索引 vector 中的值,并循环增加其值直到达到 vector 的元素数量。
-迭代器为我们处理了所有这些逻辑,这减少了重复代码并潜在的消除了混乱。另外,迭代器的实现方式提供了对多种不同的序列使用相同逻辑的灵活性,而不仅仅是像 vector 这样可索引的数据结构.让我们看看迭代器是如何做到这些的。
+迭代器为我们处理了所有这些逻辑,这减少了重复代码并消除了潜在的混乱。另外,迭代器的实现方式提供了对多种不同的序列使用相同逻辑的灵活性,而不仅仅是像 vector 这样可索引的数据结构.让我们看看迭代器是如何做到这些的。
### `Iterator` trait 和 `next` 方法
@@ -46,17 +46,19 @@ trait Iterator {
fn next(&mut self) -> Option;
- // methods with default implementations elided
+ // 此处省略了方法的默认实现
}
```
注意这里有一下我们还未讲到的新语法:`type Item` 和 `Self::Item`,他们定义了 trait 的 **关联类型**(*associated type*)。第十九章会深入讲解关联类型,不过现在只需知道这段代码表明实现 `Iterator` trait 要求同时定义一个 `Item` 类型,这个 `Item` 类型被用作 `next` 方法的返回值类型。换句话说,`Item` 类型将是迭代器返回元素的类型。
-`next` 是 `Iterator` 实现者被要求定义的唯一方法。`next` 一次返回迭代器中的一个项,封装在 `Some` 中,当迭代器结束时,它返回 `None`。如果你希望的话可以直接调用迭代器的 `next` 方法;示例 13-15 有一个测试展示了重复调用由 vector 创建的迭代器的 `next` 方法所得到的值:
+`next` 是 `Iterator` 实现者被要求定义的唯一方法。`next` 一次返回迭代器中的一个项,封装在 `Some` 中,当迭代器结束时,它返回 `None`。
+
+可以直接调用迭代器的 `next` 方法;示例 13-15 有一个测试展示了重复调用由 vector 创建的迭代器的 `next` 方法所得到的值:
文件名: src/lib.rs
-```rust,test_harness
+```rust
#[test]
fn iterator_demonstration() {
let v1 = vec![1, 2, 3];
@@ -109,7 +111,7 @@ fn iterator_sum() {
文件名: src/main.rs
-```rust
+```rust,not_desired_behavior
let v1: Vec = vec![1, 2, 3];
v1.iter().map(|x| x + 1);
@@ -134,7 +136,7 @@ and do nothing unless consumed
为了修复这个警告并消费迭代器获取有用的结果,我们将使用第十二章简要讲到的 `collect` 方法。这个方法消费迭代器并将结果收集到一个数据结构中。
-在示例 13-18 中,我们将遍历由 `map` 调用生成的迭代器的结果收集到一个 vector 中,它将会含有原始 vector 中每个元素加一的结果:
+在示例 13-18 中,我们将遍历由 `map` 调用生成的迭代器的结果收集到一个 vector 中,它将会含有原始 vector 中每个元素加 1 的结果:
文件名: src/main.rs
@@ -158,7 +160,7 @@ assert_eq!(v2, vec![2, 3, 4]);
文件名: src/lib.rs
-```rust,test_harness
+```rust
#[derive(PartialEq, Debug)]
struct Shoe {
size: u32,
@@ -195,7 +197,7 @@ fn filters_by_size() {
`shoes_in_my_size` 函数获取一个鞋子 vector 的所有权和一个鞋子大小作为参数。它返回一个只包含指定大小鞋子的 vector。
-在 `shoes_in_my_size` 函数体中调用了 `into_iter` 来创建一个获取 vector 所有权的迭代器。接着调用 `filter` 将这个迭代器适配成只含有闭包返回 `true` 元素的新迭代器。
+`shoes_in_my_size` 函数调用了 `into_iter` 来创建一个获取 vector 所有权的迭代器。接着调用 `filter` 将这个迭代器适配成一个只含有那些闭包返回 `true` 的元素的新迭代器。
闭包从环境中捕获了 `shoe_size` 变量并使用其值与每一只鞋的大小作比较,只保留指定大小的鞋子。最终,调用 `collect` 将迭代器适配器返回的值收集进一个 vector 并返回。
@@ -209,7 +211,6 @@ fn filters_by_size() {
示例 13-20 有一个 `Counter` 结构体定义和一个创建 `Counter` 实例的关联函数 `new`:
-
文件名: src/lib.rs
```rust
@@ -350,4 +351,4 @@ fn using_other_iterator_trait_methods() {
注意 `zip` 只产生四对值;理论上第五对值 `(5, None)` 从未被产生,因为 `zip` 在任一输入迭代器返回 `None` 时也返回 `None`。
-所有这些方法调用都是可能的,因为我们通过指定 `next` 如何工作来实现 `Iterator` trait 而标准库则提供其他调用 `next` 的默认方法实现。
+所有这些方法调用都是可能的,因为我们指定了 `next` 方法如何工作,而标准库则提供了其它调用 `next` 的方法的默认实现。
diff --git a/src/ch13-03-improving-our-io-project.md b/src/ch13-03-improving-our-io-project.md
index 0b63745..3c9adb1 100644
--- a/src/ch13-03-improving-our-io-project.md
+++ b/src/ch13-03-improving-our-io-project.md
@@ -1,14 +1,14 @@
## 改进 I/O 项目
-> [ch13-03-improving-our-io-project.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch13-03-improving-our-io-project.md)
+> [ch13-03-improving-our-io-project.md](https://github.com/rust-lang/book/blob/master/src/ch13-03-improving-our-io-project.md)
>
-> commit 2bcb126815a381acc3d46b0d6fc382cb4c98fbc5
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
有了这些关于迭代器的新知识,我们可以使用迭代器来改进第十二章中 I/O 项目的实现来使得代码更简洁明了。让我们看看迭代器如何能够改进 `Config::new` 函数和 `search` 函数的实现。
### 使用迭代器并去掉 `clone`
-在示例 12-6 中,我们增加了一些代码获取一个 `String` slice 并创建一个 `Config` 结构体的实例,他们索引 slice 中的值并克隆这些值以便 `Config` 结构体可以拥有这些值。在示例 13-24 中原原本本的重现了第十二章结尾示例 12-23 中 `Config::new` 函数的实现:
+在示例 12-6 中,我们增加了一些代码获取一个 `String` slice 并创建一个 `Config` 结构体的实例,他们索引 slice 中的值并克隆这些值以便 `Config` 结构体可以拥有这些值。在示例 13-24 中重现了第十二章结尾示例 12-23 中 `Config::new` 函数的实现:
文件名: src/lib.rs
@@ -31,11 +31,11 @@ impl Config {
示例 13-24:重现第十二章结尾的 `Config::new` 函数
-这时可以不必担心低效的 `clone` 调用了,因为将来可以去掉他们。好吧,就是现在!
+那时我们说过不必担心低效的 `clone` 调用了,因为将来可以对他们进行改进。好吧,就是现在!
起初这里需要 `clone` 的原因是参数 `args` 中有一个 `String` 元素的 slice,而 `new` 函数并不拥有 `args`。为了能够返回 `Config` 实例的所有权,我们需要克隆 `Config` 中字段 `query` 和 `filename` 的值,这样 `Config` 实例就能拥有这些值。
-通过迭代器的新知识,我们可以将 `new` 函数改为获取一个有所有权的迭代器作为参数而不是借用 slice。我们将使用迭代器功能之前检查 slice 长度和索引特定位置的代码。这会明确 `Config::new` 的工作因为迭代器会负责访问这些值。
+在学习了迭代器之后,我们可以将 `new` 函数改为获取一个有所有权的迭代器作为参数而不是借用 slice。我们将使用迭代器功能之前检查 slice 长度和索引特定位置的代码。这会明确 `Config::new` 的工作因为迭代器会负责访问这些值。
一旦 `Config::new` 获取了迭代器的所有权并不再使用借用的索引操作,就可以将迭代器中的 `String` 值移动到 `Config` 中,而不是调用 `clone` 分配新的空间。
@@ -58,9 +58,7 @@ fn main() {
}
```
-我们会修改第十二章结尾示例 12-24 中的 `main` 函数的开头为示例 13-25 中的代码。直到同时更新 `Config::new` 这些代码还不能编译:
-
-将他们改为如示例 13-25 所示:
+修改第十二章结尾示例 12-24 中的 `main` 函数的开头为示例 13-25 中的代码。在更新 `Config::new` 之前这些代码还不能编译:
文件名: src/main.rs
@@ -100,6 +98,7 @@ impl Config {
文件名: src/lib.rs
```rust
+# fn main() {}
# use std::env;
#
# struct Config {
@@ -129,13 +128,13 @@ impl Config {
}
```
-示例 13-27:修改 `Config::new` 的函数体为使用迭代器方法
+示例 13-27:修改 `Config::new` 的函数体来使用迭代器方法
请记住 `env::args` 返回值的第一个值是程序的名称。我们希望忽略它并获取下一个值,所以首先调用 `next` 并不对返回值做任何操作。之后对希望放入 `Config` 中字段 `query` 调用 `next`。如果 `next` 返回 `Some`,使用 `match` 来提取其值。如果它返回 `None`,则意味着没有提供足够的参数并通过 `Err` 值提早返回。对 `filename` 值进行同样的操作。
### 使用迭代器适配器来使代码更简明
-I/O 项目中其他可以利用迭代器优势的地方位于 `search` 函数,在示例 13-28 中重现了第十二章结尾示例 12-19 中此函数的定义:
+I/O 项目中其他可以利用迭代器的地方是 `search` 函数,示例 13-28 中重现了第十二章结尾示例 12-19 中此函数的定义:
文件名: src/lib.rs
@@ -153,9 +152,9 @@ pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
}
```
-示例 13-28:第十二章结尾 `search` 函数的定义
+示例 13-28:示例 12-19 中 `search` 函数的定义
-可以通过使用迭代器适配器方法来编写更短的代码。这也避免了一个可变的中间 `results` vector 的使用。函数式编程风格倾向于最小化可变状态的数量来使代码更简洁。去掉可变状态可能会使得将来进行并行搜索的增强变得更容易,因为我们不必管理 `results` vector 的并发访问。示例 13-29 展示了该变化:
+可以通过使用迭代器适配器方法来编写更简明的代码。这也避免了一个可变的中间 `results` vector 的使用。函数式编程风格倾向于最小化可变状态的数量来使代码更简洁。去掉可变状态可能会使得将来进行并行搜索的增强变得更容易,因为我们不必管理 `results` vector 的并发访问。示例 13-29 展示了该变化:
文件名: src/lib.rs
@@ -169,8 +168,8 @@ pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
示例 13-29:在 `search` 函数实现中使用迭代器适配器
-回忆 `search` 函数的目的是返回所有 `contents` 中包含 `query` 的行。类似于示例 13-19 中的 `filter` 例子,可以使用 `filter` 适配器只保留 `line.contains(query)` 为真的那些行。接着使用 `collect` 将匹配行收集到另一个 vector 中。这样就容易多了!请随意对 `search_case_insensitive` 函数做出同样的使用迭代器方法的修改。
+回忆 `search` 函数的目的是返回所有 `contents` 中包含 `query` 的行。类似于示例 13-19 中的 `filter` 例子,可以使用 `filter` 适配器只保留 `line.contains(query)` 返回 `true` 的那些行。接着使用 `collect` 将匹配行收集到另一个 vector 中。这样就容易多了!尝试对 `search_case_insensitive` 函数做出同样的使用迭代器方法的修改吧。
-接下来的逻辑问题就是在代码中应该选择哪种风格:示例 13-28 中的原始实现,或者是示例 13-29 中使用迭代器的版本。大部分 Rust 程序员倾向于使用迭代器风格。开始这有点难以理解,不过一旦你对不同迭代器的工作方式有了感觉之后,迭代器可能会更容易理解。相比摆弄不同的循环并创建新 vector,(迭代器)代码则更关注循环的目的。这抽象出了那些老生常谈的代码,这样就更容易看清代码所特有的概念,比如迭代器中每个元素必须面对的过滤条件。
+接下来的逻辑问题就是在代码中应该选择哪种风格:是使用示例 13-28 中的原始实现还是使用示例 13-29 中使用迭代器的版本?大部分 Rust 程序员倾向于使用迭代器风格。开始这有点难以理解,不过一旦你对不同迭代器的工作方式有了感觉之后,迭代器可能会更容易理解。相比摆弄不同的循环并创建新 vector,(迭代器)代码则更关注循环的目的。这抽象出了那些老生常谈的代码,这样就更容易看清代码所特有的概念,比如迭代器中每个元素必须面对的过滤条件。
不过这两种实现真的完全等同吗?直觉上的假设是更底层的循环会更快一些。让我们聊聊性能吧。
diff --git a/src/ch13-04-performance.md b/src/ch13-04-performance.md
index de39e1d..c9ba7f7 100644
--- a/src/ch13-04-performance.md
+++ b/src/ch13-04-performance.md
@@ -1,10 +1,10 @@
## 性能对比:循环 VS 迭代器
-> [ch13-04-performance.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch13-04-performance.md)
+> [ch13-04-performance.md](https://github.com/rust-lang/book/blob/master/src/ch13-04-performance.md)
>
-> commit 2bcb126815a381acc3d46b0d6fc382cb4c98fbc5
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
-为了决定使用哪个实现,我们需要知道哪个版本的 `search` 函数更快:直接使用 `for` 循环的版本还是使用迭代器的版本。
+为了决定使用哪个实现,我们需要知道哪个版本的 `search` 函数更快一些:是直接使用 `for` 循环的版本还是使用迭代器的版本。
我们运行了一个性能测试,通过将阿瑟·柯南·道尔的“福尔摩斯探案集”的全部内容加载进 `String` 并寻找其中的单词 “the”。如下是 `for` 循环版本和迭代器版本的 `search` 函数的性能测试结果:
@@ -15,7 +15,7 @@ test bench_search_iter ... bench: 19,234,900 ns/iter (+/- 657,200)
结果迭代器版本还要稍微快一点!这里我们将不会查看性能测试的代码,我们的目的并不是为了证明他们是完全等同的,而是得出一个怎样比较这两种实现方式性能的基本思路。
-对于一个更全面的性能测试,将会检查不同长度的文本、不同的搜索单词、不同长度的单词和所有其他的可变情况。这里所要表达的是:迭代器,作为一个高级的抽象,被编译成了与手写的底层代码大体一致性能代码。迭代器是 Rust 的 **零成本抽象**(*zero-cost abstractions*)之一,它意味着抽象并不会强加运行时开销,它与本贾尼·斯特劳斯特卢普,C++ 的设计和实现者所定义的 **零开销**(*zero-overhead*)如出一辙:
+对于一个更全面的性能测试,将会检查不同长度的文本、不同的搜索单词、不同长度的单词和所有其他的可变情况。这里所要表达的是:迭代器,作为一个高级的抽象,被编译成了与手写的底层代码大体一致性能代码。迭代器是 Rust 的 **零成本抽象**(*zero-cost abstractions*)之一,它意味着抽象并不会引入运行时开销,它与本贾尼·斯特劳斯特卢普(C++ 的设计和实现者)在 “Foundations of C++”(2012) 中所定义的 **零开销**(*zero-overhead*)如出一辙:
> In general, C++ implementations obey the zero-overhead principle: What you don’t use, you don’t pay for. And further: What you do use, you couldn’t hand code any better.
>
diff --git a/src/ch14-00-more-about-cargo.md b/src/ch14-00-more-about-cargo.md
index f9d54ae..28f131a 100644
--- a/src/ch14-00-more-about-cargo.md
+++ b/src/ch14-00-more-about-cargo.md
@@ -1,10 +1,10 @@
# 进一步认识 Cargo 和 Crates.io
-> [ch14-00-more-about-cargo.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch14-00-more-about-cargo.md)
+> [ch14-00-more-about-cargo.md](https://github.com/rust-lang/book/blob/master/src/ch14-00-more-about-cargo.md)
>
-> commit ff93f82ff63ade5a352d9ccc430945d4ec804cdf
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
-目前为止我们只使用过 Cargo 构建、运行和测试代码的最基本功能,不过它还可以做到更多。这里我们将了解一些 Cargo 其他更为高级的功能,他们将展示如何:
+目前为止我们只使用过 Cargo 构建、运行和测试代码这些最基本的功能,不过它还可以做到更多。本章会讨论 Cargo 其他一些更为高级的功能,我们将展示如何:
* 使用发布配置来自定义构建
* 将库发布到 [crates.io](https://crates.io)
@@ -12,4 +12,4 @@
* 从 [crates.io](https://crates.io) 安装二进制文件
* 使用自定义的命令来扩展 Cargo
-相比本章能够涉及的工作 Cargo 甚至还可以做到更多,关于其功能的全部解释,请查看 [其文档](http://doc.rust-lang.org/cargo/)
\ No newline at end of file
+Cargo 的功能不止本章所介绍的,关于其功能的全部解释,请查看 [文档](http://doc.rust-lang.org/cargo/)
\ No newline at end of file
diff --git a/src/ch14-01-release-profiles.md b/src/ch14-01-release-profiles.md
index d772793..cf71ddd 100644
--- a/src/ch14-01-release-profiles.md
+++ b/src/ch14-01-release-profiles.md
@@ -1,14 +1,14 @@
## 采用发布配置自定义构建
-> [ch14-01-release-profiles.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch14-01-release-profiles.md)
+> [ch14-01-release-profiles.md](https://github.com/rust-lang/book/blob/master/src/ch14-01-release-profiles.md)
>
-> commit ff93f82ff63ade5a352d9ccc430945d4ec804cdf
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
-在 Rust 中 **发布配置**(*release profiles*)是预定义的、可定制的带有不同选项的配置,他们允许程序员更多的控制代码编译的多种选项。每一个配置都彼此相互独立。
+在 Rust 中 **发布配置**(*release profiles*)是预定义的、可定制的带有不同选项的配置,他们允许程序员更灵活地控制代码编译的多种选项。每一个配置都彼此相互独立。
Cargo 有两个主要的配置:运行 `cargo build` 时采用的 `dev` 配置和运行 `cargo build --release` 的 `release` 配置。`dev` 配置被定义为开发时的好的默认配置,`release` 配置则有着良好的发布构建的默认配置。
-我们应该很熟悉这些配置名称因为他们出现在构建的输出中,这会展示构建所使用的配置:
+这些配置名称可能很眼熟,因为它们出现在构建的输出中:
```text
$ cargo build
@@ -19,7 +19,7 @@ $ cargo build --release
构建输出中的 `dev` 和 `release` 表明编译器在使用不同的配置。
-Cargo 对每一个配置都有默认设置,当项目的 *Cargo.toml* 文件中没有任何 `[profile.*]` 部分的时候。通过增加任何希望定制的配置对应的 `[profile.*]` 部分,我们可以选择覆盖任意默认设置的子集。例如,如下是 `dev` 和 `release` 配置的 `opt-level` 设置的默认值:
+当项目的 *Cargo.toml* 文件中没有任何 `[profile.*]` 部分的时候,Cargo 会对每一个配置都采用默认设置。通过增加任何希望定制的配置对应的 `[profile.*]` 部分,我们可以选择覆盖任意默认设置的子集。例如,如下是 `dev` 和 `release` 配置的 `opt-level` 设置的默认值:
文件名: Cargo.toml
diff --git a/src/ch14-02-publishing-to-crates-io.md b/src/ch14-02-publishing-to-crates-io.md
index 2e3c0fd..d74936c 100644
--- a/src/ch14-02-publishing-to-crates-io.md
+++ b/src/ch14-02-publishing-to-crates-io.md
@@ -1,24 +1,23 @@
## 将 crate 发布到 Crates.io
-> [ch14-02-publishing-to-crates-io.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch14-02-publishing-to-crates-io.md)
+> [ch14-02-publishing-to-crates-io.md](https://github.com/rust-lang/book/blob/master/src/ch14-02-publishing-to-crates-io.md)
>
-> commit ff93f82ff63ade5a352d9ccc430945d4ec804cdf
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
我们曾经在项目中使用 [crates.io](https://crates.io) 上的包作为依赖,不过你也可以通过发布自己的包来向它人分享代码。[crates.io](https://crates.io) 用来分发包的源代码,所以它主要托管开源代码。
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` 函数的文档注释:
文件名: src/lib.rs
```rust,ignore
-/// Adds one to the number given.
+/// 将给定的数字加一
///
/// # Examples
///
@@ -34,7 +33,7 @@ pub fn add_one(x: i32) -> i32 {
示例 14-1:一个函数的文档注释
-这里,我们提供了一个 `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 所示:
@@ -78,10 +77,10 @@ test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
```rust,ignore
//! # My Crate
//!
-//! `my_crate` is a collection of utilities to make performing certain
-//! calculations more convenient.
+//! `my_crate` 是一个使得特定计算更方便的
+//! 工具集合
-/// Adds one to the number given.
+/// 将给定的数字加一。
// --snip--
```
@@ -99,30 +98,30 @@ test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
### 使用 `pub use` 导出合适的公有 API
-第七章介绍了如何使用 `mod` 关键字来将代码组织进模块中,如何使用 `pub` 关键字将项变为公有,和如何使用 `use` 关键字将项引入作用域。然而对你开发来说很有道理的结果可能对用户来说就不太方便了。你可能希望将结构组织进有多个层次的层级中,不过想要使用被定义在很深层级中的类型的人可能很难发现这些类型是否存在。他们也可能会厌烦 `use my_crate::some_module::another_module::UsefulType;` 而不是 `use my_crate::UsefulType;` 来使用类型。
+第七章介绍了如何使用 `mod` 关键字来将代码组织进模块中,如何使用 `pub` 关键字将项变为公有,和如何使用 `use` 关键字将项引入作用域。然而你开发时候使用的文件架构可能并不方便用户。你的结构可能是一个包含多个层级的分层结构,不过这对于用于来说并不方便。这是因为想要使用被定义在很深层级中的类型的人可能很难发现这些类型的存在。他们也可能会厌烦使用 `use my_crate::some_module::another_module::UsefulType;` 而不是 `use my_crate::UsefulType;` 来使用类型。
公有 API 的结构是你发布 crate 时主要需要考虑的。crate 用户没有你那么熟悉其结构,并且如果模块层级过大他们可能会难以找到所需的部分。
-好消息是,如果结果对于用户来说 **不是** 很方便,你也无需重新安排内部组织:你可以选择使用 `pub use` 重导出(re-export)项来使公有结构不同于私有结构。重导出获取位于一个位置的公有项并将其公开到另一个位置,好像它就定义在这个新位置一样。
+好消息是,即使文件结构对于用户来说 **不是** 很方便,你也无需重新安排内部组织:你可以选择使用 `pub use` 重导出(re-export)项来使公有结构不同于私有结构。重导出获取位于一个位置的公有项并将其公开到另一个位置,好像它就定义在这个新位置一样。
-例如,假设我们创建了一个模块化了充满艺术化气息的库 `art`。在这个库中是一个包含两个枚举 `PrimaryColor` 和 `SecondaryColor` 的模块 `kinds`,以及一个包含函数 `mix` 的模块 `utils`,如示例 14-3 所示:
+例如,假设我们创建了一个描述美术信息的库 `art`。这个库中包含了一个有两个枚举 `PrimaryColor` 和 `SecondaryColor` 的模块 `kinds`,以及一个包含函数 `mix` 的模块 `utils`,如示例 14-3 所示:
文件名: src/lib.rs
```rust,ignore
//! # Art
//!
-//! A library for modeling artistic concepts.
+//! 一个描述美术信息的库。
pub mod kinds {
- /// The primary colors according to the RYB color model.
+ /// 采用 RGB 色彩模式的主要颜色。
pub enum PrimaryColor {
Red,
Yellow,
Blue,
}
- /// The secondary colors according to the RYB color model.
+ /// 采用 RGB 色彩模式的次要颜色。
pub enum SecondaryColor {
Orange,
Green,
@@ -133,8 +132,8 @@ pub mod kinds {
pub mod utils {
use kinds::*;
- /// Combines two primary colors in equal amounts to create
- /// a secondary color.
+ /// 等量的混合两个主要颜色
+ /// 来创建一个次要颜色。
pub fn mix(c1: PrimaryColor, c2: PrimaryColor) -> SecondaryColor {
// --snip--
}
@@ -149,15 +148,13 @@ pub mod utils {
图 14-3:包含 `kinds` 和 `utils` 模块的库 `art` 的文档首页
-注意 `PrimaryColor` 和 `SecondaryColor` 类型没有在首页中列出,`mix` 函数也是。必须点击 `kinds` 或 `utils` 才能看到他们。
+注意 `PrimaryColor` 和 `SecondaryColor` 类型、以及 `mix` 函数都没有在首页中列出。我们必须点击 `kinds` 或 `utils` 才能看到他们。
另一个依赖这个库的 crate 需要 `use` 语句来导入 `art` 中的项,这包含指定其当前定义的模块结构。示例 14-4 展示了一个使用 `art` crate 中 `PrimaryColor` 和 `mix` 项的 crate 的例子:
文件名: src/main.rs
```rust,ignore
-extern crate art;
-
use art::kinds::PrimaryColor;
use art::utils::mix;
@@ -179,7 +176,7 @@ fn main() {
```rust,ignore
//! # Art
//!
-//! A library for modeling artistic concepts.
+//! 一个描述美术信息的库。
pub use kinds::PrimaryColor;
pub use kinds::SecondaryColor;
@@ -202,13 +199,11 @@ pub mod utils {
图 14-10:`art` 文档的首页,这里列出了重导出的项
-`art` crate 的用户仍然可以看见和选择使用示例 14-3 中的内部结构,或者可以使用示例 14-4 中更为方便的结构,如示例 14-6 所示:
+`art` crate 的用户仍然可以看见和选择使用示例 14-4 中的内部结构,或者可以使用示例 14-5 中更为方便的结构,如示例 14-6 所示:
文件名: src/main.rs
```rust,ignore
-extern crate art;
-
use art::PrimaryColor;
use art::mix;
@@ -221,11 +216,11 @@ fn main() {
对于有很多嵌套模块的情况,使用 `pub use` 将类型重导出到顶级结构对于使用 crate 的人来说将会是大为不同的体验。
-创建一个有用的公有 API 结构更像是一门艺术而非科学,你可以反复检视他们来找出最适合用户的 API。选择 `pub use` 提供了解耦组织 crate 内部结构和与终端用户体现的灵活性。观察一些你所安装的 crate 的代码来看看其内部结构是否不同于公有 API。
+创建一个有用的公有 API 结构更像是一门艺术而非科学,你可以反复检视他们来找出最适合用户的 API。`pub use` 提供了解耦组织 crate 内部结构和与终端用户体现的灵活性。观察一些你所安装的 crate 的代码来看看其内部结构是否不同于公有 API。
### 创建 Crates.io 账号
-在你可以发布任何 crate 之前,需要在 [crates.io](https://crates.io) 上注册账号并获取一个 API token。为此,访问位于 [crates.io](https://crates.io) 的首页并使用 GitHub 账号登陆————目前 GitHub 账号是必须的,不过将来该网站可能会支持其他创建账号的方法。一旦登陆之后,查看位于 [https://crates.io/me/](https://crates.io/me/) 的账户设置页面并获取 API token。接着使用该 API token 运行 `cargo login` 命令,像这样:
+在你可以发布任何 crate 之前,需要在 [crates.io](https://crates.io) 上注册账号并获取一个 API token。为此,访问位于 [crates.io](https://crates.io) 的首页并使用 GitHub 账号登陆。(目前 GitHub 账号是必须的,不过将来该网站可能会支持其他创建账号的方法)一旦登陆之后,查看位于 [https://crates.io/me/](https://crates.io/me/) 的账户设置页面并获取 API token。接着使用该 API token 运行 `cargo login` 命令,像这样:
```text
$ cargo login abcdefghijklmnopqrstuvwxyz012345
@@ -259,7 +254,10 @@ error: api errors: missing or empty metadata fields: description, license.
这是因为我们缺少一些关键信息:关于该 crate 用途的描述和用户可能在何种条款下使用该 crate 的 license。为了修正这个错误,需要在 *Cargo.toml* 中引入这些信息。
-描述通常是一两句话,因为它会出现在 crate 的搜索结果中和 crate 页面里。对于 `license` 字段,你需要一个 **license 标识符值**(*license identifier value*)。Linux 基金会位于 *http://spdx.org/licenses/* 的 Software Package Data Exchange (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/
文件名: Cargo.toml
@@ -271,7 +269,7 @@ license = "MIT"
如果你希望使用不存在于 SPDX 的 license,则需要将 license 文本放入一个文件,将该文件包含进项目中,接着使用 `license-file` 来指定文件名而不是使用 `license` 字段。
-关于项目所适用的 license 指导超出了本书的范畴。很多 Rust 社区成员选择与 Rust 自身相同的 license,这是一个双许可的 `MIT OR Apache-2.0` ———— 这展示了也可以通过 `OR` 来分隔来为项目指定多个 license 标识符。
+关于项目所适用的 license 指导超出了本书的范畴。很多 Rust 社区成员选择与 Rust 自身相同的 license,这是一个双许可的 `MIT OR Apache-2.0`。这个实践展示了也可以通过 `OR` 分隔为项目指定多个 license 标识符。
那么,有了唯一的名称、版本号、由 `cargo new` 新建项目时增加的作者信息、描述和所选择的 license,已经准备好发布的项目的 *Cargo.toml* 文件可能看起来像这样:
@@ -294,7 +292,7 @@ license = "MIT OR Apache-2.0"
现在我们创建了一个账号,保存了 API token,为 crate 选择了一个名字,并指定了所需的元数据,你已经准备好发布了!发布 crate 会上传特定版本的 crate 到 [crates.io](https://crates.io) 以供他人使用。
-发布 crate 时请多加小心,因为发布是 **永久性的**(*permanent*)。对应版本不可能被覆盖,其代码也不可能被删除。[crates.io](https://crates.io) 的一个主要目标是作为一个代码的永久文档服务器,这样所有依赖 [crates.io](https://crates.io) 中 crate 的项目都能一直正常工作。允许删除版本将不可能满足这个目标。然而,可以被发布的版本号却没有限制。
+发布 crate 时请多加小心,因为发布是 **永久性的**(*permanent*)。对应版本不可能被覆盖,其代码也不可能被删除。[crates.io](https://crates.io) 的一个主要目标是作为一个存储代码的永久文档服务器,这样所有依赖 [crates.io](https://crates.io) 中的 crate 的项目都能一直正常工作。而允许删除版本没办法达成这个目标。然而,可以被发布的版本号却没有限制。
再次运行 `cargo publish` 命令。这次它应该会成功:
diff --git a/src/ch14-03-cargo-workspaces.md b/src/ch14-03-cargo-workspaces.md
index da5fb44..4c4463a 100644
--- a/src/ch14-03-cargo-workspaces.md
+++ b/src/ch14-03-cargo-workspaces.md
@@ -1,12 +1,14 @@
## Cargo 工作空间
-> [ch14-03-cargo-workspaces.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch14-03-cargo-workspaces.md)
+> [ch14-03-cargo-workspaces.md](https://github.com/rust-lang/book/blob/master/src/ch14-03-cargo-workspaces.md)
>
-> commit a59537604248f2970e0831d5ead9f6fac2cdef84
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
第十二章中,我们构建一个包含二进制 crate 和库 crate 的包。你可能会发现,随着项目开发的深入,库 crate 持续增大,而你希望将其进一步拆分成多个库 crate。对于这种情况,Cargo 提供了一个叫 **工作空间**(*workspaces*)的功能,它可以帮助我们管理多个相关的协同开发的包。
-**工作空间** 是一系列共享同样的 *Cargo.lock* 和输出目录的包。让我们使用工作空间创建一个项目,这里采用常见的代码这样就可以关注工作空间的结构了。有多种组织工作空间的方式;我们将展示一个常用方法。我们的工作空间有一个二进制项目和两个库。二进制项目会提供作为命令行工具的主要功能,它会依赖另两个库。一个库会提供 `add_one` 方法而第二个会提供 `add_two` 方法。这三个 crate 将会是相同工作空间的一部分。让我们以新建工作空间目录开始:
+### 创建工作空间
+
+**工作空间** 是一系列共享同样的 *Cargo.lock* 和输出目录的包。让我们使用工作空间创建一个项目 —— 这里采用常见的代码以便可以关注工作空间的结构。有多种组织工作空间的方式;我们将展示一个常用方法。我们的工作空间有一个二进制项目和两个库。二进制项目会提供主要功能,并会依赖另两个库。一个库会提供 `add_one` 方法而第二个会提供 `add_two` 方法。这三个 crate 将会是相同工作空间的一部分。让我们以新建工作空间目录开始:
```text
$ mkdir add
@@ -28,7 +30,7 @@ members = [
接下来,在 *add* 目录运行 `cargo new` 新建 `adder` 二进制 crate:
```text
-$ cargo new --bin adder
+$ cargo new adder
Created binary (application) `adder` project
```
@@ -64,7 +66,7 @@ members = [
接着新生成一个叫做 `add-one` 的库:
```text
-$ cargo new add-one
+$ cargo new add-one --lib
Created library `add-one` project
```
@@ -106,12 +108,12 @@ add-one = { path = "../add-one" }
工作空间中的 crate 不必相互依赖,所以仍需显式地表明工作空间中 crate 的依赖关系。
-接下来,在 `adder` crate 中使用 `add-one` crate 的函数 `add_one`。打开 *adder/src/main.rs* 在顶部增加一行 `extern crate` 将新 `add-one` 库 crate 引入作用域。接着修改 `main` 函数来调用 `add_one` 函数,如示例 14-7 所示:
+接下来,在 `adder` crate 中使用 `add-one` crate 的函数 `add_one`。打开 *adder/src/main.rs* 在顶部增加一行 `use` 将新 `add-one` 库 crate 引入作用域。接着修改 `main` 函数来调用 `add_one` 函数,如示例 14-7 所示:
文件名: adder/src/main.rs
```rust,ignore
-extern crate add_one;
+use add_one;
fn main() {
let num = 10;
@@ -154,7 +156,7 @@ Hello, world! 10 plus one is 11!
rand = "0.3.14"
```
-现在就可以在 *add-one/src/lib.rs* 中增加 `extern crate rand;` 了,接着在 *add* 目录运行 `cargo build` 构建整个工作空间就会引入并编译 `rand` crate:
+现在就可以在 *add-one/src/lib.rs* 中增加 `use rand;` 了,接着在 *add* 目录运行 `cargo build` 构建整个工作空间就会引入并编译 `rand` crate:
```text
$ cargo build
@@ -167,7 +169,7 @@ $ cargo build
Finished dev [unoptimized + debuginfo] target(s) in 10.18 secs
```
-现在顶级的 *Cargo.lock* 包含了 `add-one` 的 `rand` 依赖的信息。然而,即使 `rand` 被用于工作空间的某处,也不能在其他 crate 中使用它,除非也在他们的 *Cargo.toml* 中加入 `rand`。例如,如果在顶级的 `adder` crate 的 *adder/src/main.rs* 中增加 `extern crate rand;`,会得到一个错误:
+现在顶级的 *Cargo.lock* 包含了 `add-one` 的 `rand` 依赖的信息。然而,即使 `rand` 被用于工作空间的某处,也不能在其他 crate 中使用它,除非也在他们的 *Cargo.toml* 中加入 `rand`。例如,如果在顶级的 `adder` crate 的 *adder/src/main.rs* 中增加 `use rand;`,会得到一个错误:
```text
$ cargo build
@@ -176,7 +178,7 @@ error: use of unstable library feature 'rand': use `rand` from crates.io (see
issue #27703)
--> adder/src/main.rs:1:1
|
-1 | extern crate rand;
+1 | use rand;
```
为了修复这个错误,修改顶级 `adder` crate 的 *Cargo.toml* 来表明 `rand` 也是这个 crate 的依赖。构建 `adder` crate 会将 `rand` 加入到 *Cargo.lock* 中 `adder` 的依赖列表中,但是这并不会下载 `rand` 的额外拷贝。Cargo 确保了工作空间中任何使用 `rand` 的 crate 都采用相同的版本。在整个工作空间中使用相同版本的 `rand` 节省了空间,因为这样就无需多个拷贝并确保了工作空间中的 crate 将是相互兼容的。
@@ -257,4 +259,4 @@ test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
现在尝试以类似 `add-one` crate 的方式向工作空间增加 `add-two` crate 来作为更多的练习!
-随着项目增长,考虑使用工作空间:每一个更小的组件比一大块代码要容易理解。将 crate 保持在工作空间中更易于协调他们的改变,如果他们一起运行并经常需要同时被修改的话。
+随着项目增长,考虑使用工作空间:每一个更小的组件比一大块代码要容易理解。如果它们经常需要同时被修改的话,将 crate 保持在工作空间中更易于协调他们的改变。
diff --git a/src/ch14-04-installing-binaries.md b/src/ch14-04-installing-binaries.md
index 9f956fe..fa93f9b 100644
--- a/src/ch14-04-installing-binaries.md
+++ b/src/ch14-04-installing-binaries.md
@@ -1,10 +1,10 @@
## 使用 `cargo install` 从 Crates.io 安装二进制文件
-> [ch14-04-installing-binaries.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch14-04-installing-binaries.md)
+> [ch14-04-installing-binaries.md](https://github.com/rust-lang/book/blob/master/src/ch14-04-installing-binaries.md)
>
-> commit ff93f82ff63ade5a352d9ccc430945d4ec804cdf
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
-`cargo install` 命令用于在本地安装和使用二进制 crate。它并不打算替换系统中的包;它意在作为一个方便 Rust 开发者们安装其他人已经在 [crates.io](https://crates.io) 上共享的工具的手段。只有拥有二进制目标文件的包能够被安装。二进制目标文件是在 crate 有 *src/main.rs* 或者其他指定为二进制文件时所创建的可执行程序,这不同于自身不能执行但适合包含在其他程序中的库目标文件。通常 crate 的 *README* 文件中有该 crate 是库、二进制目标还是两者都是的信息。
+`cargo install` 命令用于在本地安装和使用二进制 crate。它并不打算替换系统中的包;它意在作为一个方便 Rust 开发者们安装其他人已经在 [crates.io](https://crates.io) 上共享的工具的手段。只有拥有二进制目标文件的包能够被安装。**二进制目标** 文件是在 crate 有 *src/main.rs* 或者其他指定为二进制文件时所创建的可执行程序,这不同于自身不能执行但适合包含在其他程序中的库目标文件。通常 crate 的 *README* 文件中有该 crate 是库、二进制目标还是两者都是的信息。
所有来自 `cargo install` 的二进制文件都安装到 Rust 安装根目录的 *bin* 文件夹中。如果你使用 *rustup.rs* 安装的 Rust 且没有自定义任何配置,这将是 `$HOME/.cargo/bin`。确保将这个目录添加到 `$PATH` 环境变量中就能够运行通过 `cargo install` 安装的程序了。
diff --git a/src/ch14-05-extending-cargo.md b/src/ch14-05-extending-cargo.md
index 26946b8..9d66dcd 100644
--- a/src/ch14-05-extending-cargo.md
+++ b/src/ch14-05-extending-cargo.md
@@ -1,10 +1,10 @@
## Cargo 自定义扩展命令
-> [ch14-05-extending-cargo.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch14-05-extending-cargo.md)
+> [ch14-05-extending-cargo.md](https://github.com/rust-lang/book/blob/master/src/ch14-05-extending-cargo.md)
>
-> commit ff93f82ff63ade5a352d9ccc430945d4ec804cdf
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
-Cargo 被设计为可以通过新的子命令而无须修改 Cargo 自身来进行扩展。如果 `$PATH` 中有类似 `cargo-something` 的二进制文件,就可以通过 `cargo something` 来像 Cargo 子命令一样运行它。像这样的自定义命令也可以运行 `cargo --list` 来展示出来。能够通过 `cargo install` 向 Cargo 安装扩展并可以如内建 Cargo 工具那样运行他们是 Cargo 设计上的一个非常方便的优点!
+Cargo 的设计使得开发者可以通过新的子命令来对 Cargo 进行扩展,而无需修改 Cargo 本身。如果 `$PATH` 中有类似 `cargo-something` 的二进制文件,就可以通过 `cargo something` 来像 Cargo 子命令一样运行它。像这样的自定义命令也可以运行 `cargo --list` 来展示出来。能够通过 `cargo install` 向 Cargo 安装扩展并可以如内建 Cargo 工具那样运行他们是 Cargo 设计上的一个非常方便的优点!
## 总结
diff --git a/src/ch15-00-smart-pointers.md b/src/ch15-00-smart-pointers.md
index cb11eb4..3251288 100644
--- a/src/ch15-00-smart-pointers.md
+++ b/src/ch15-00-smart-pointers.md
@@ -1,65 +1,25 @@
# 智能指针
-> [ch15-00-smart-pointers.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch15-00-smart-pointers.md)
+> [ch15-00-smart-pointers.md](https://github.com/rust-lang/book/blob/master/src/ch15-00-smart-pointers.md)
>
-> commit 68267b982a226fa252e9afa1a5029396ccf5fa03
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
-**指针** (*pointer*)是一个包含内存地址的变量的通用概念。这个地址引用,或 “指向”(points at)一些其他数据。Rust 中最常见的指针是第四章介绍的 **引用**(*reference*)。引用以 `&` 符号为标志并借用了他们所指向的值。除了引用数据它们没有任何其他特殊功能。它们也没有任何额外开销,所以应用的最多。
+**指针** (*pointer*)是一个包含内存地址的变量的通用概念。这个地址引用,或 “指向”(points at)一些其他数据。Rust 中最常见的指针是第四章介绍的 **引用**(*reference*)。引用以 `&` 符号为标志并借用了他们所指向的值。除了引用数据没有任何其他特殊功能。它们也没有任何额外开销,所以应用的最多。
另一方面,**智能指针**(*smart pointers*)是一类数据结构,他们的表现类似指针,但是也拥有额外的元数据和功能。智能指针的概念并不为 Rust 所独有;其起源于 C++ 并存在于其他语言中。Rust 标准库中不同的智能指针提供了多于引用的额外功能。本章将会探索的一个例子便是 **引用计数** (*reference counting*)智能指针类型,其允许数据有多个所有者。引用计数智能指针记录总共有多少个所有者,并当没有任何所有者时负责清理数据。
-
-
-
-
-
-
在 Rust 中,普通引用和智能指针的一个额外的区别是引用是一类只借用数据的指针;相反大部分情况,智能指针 **拥有** 他们指向的数据。
实际上本书中已经出现过一些智能指针,比如第八章的 `String` 和 `Vec`,虽然当时我们并不这么称呼它们。这些类型都属于智能指针因为它们拥有一些数据并允许你修改它们。它们也带有元数据(比如他们的容量)和额外的功能或保证(`String` 的数据总是有效的 UTF-8 编码)。
-
-
-
智能指针通常使用结构体实现。智能指针区别于常规结构体的显著特性在于其实现了 `Deref` 和 `Drop` trait。`Deref` trait 允许智能指针结构体实例表现的像引用一样,这样就可以编写既用于引用又用于智能指针的代码。`Drop` trait 允许我们自定义当智能指针离开作用域时运行的代码。本章会讨论这些 trait 以及为什么对于智能指针来说他们很重要。
考虑到智能指针是一个在 Rust 经常被使用的通用设计模式,本章并不会覆盖所有现存的智能指针。很多库都有自己的智能指针而你也可以编写属于你自己的智能指针。这里将会讲到的是来自标准库中最常用的一些:
-
-
-
* `Box`,用于在堆上分配值
* `Rc`,一个引用计数类型,其数据可以有多个所有者
* `Ref` 和 `RefMut`,通过 `RefCell` 访问,一个在运行时而不是在编译时执行借用规则的类型。
-
-
-
-同时我们会涉及 **内部可变性**(*interior mutability*)模式,这时不可变类型暴露出改变其内部值的 API。我们也会讨论 **引用循环**(*reference cycles*)会如何泄露内存,以及如何避免。
+另外我们会涉及 **内部可变性**(*interior mutability*)模式,这时不可变类型暴露出改变其内部值的 API。我们也会讨论 **引用循环**(*reference cycles*)会如何泄露内存,以及如何避免。
让我们开始吧!
diff --git a/src/ch15-01-box.md b/src/ch15-01-box.md
index 0c5703d..272bf89 100644
--- a/src/ch15-01-box.md
+++ b/src/ch15-01-box.md
@@ -1,23 +1,18 @@
## `Box` 在堆上存储数据,并且可确定大小
-> [ch15-01-box.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch15-01-box.md)
+> [ch15-01-box.md](https://github.com/rust-lang/book/blob/master/src/ch15-01-box.md)
>
-> commit 0905e41f7387b60865e6eac744e31a7f7b46edf5
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
最简单直接的智能指针是 *box*,其类型是 `Box`。 box 允许你将一个值放在堆上而不是栈上。留在栈上的则是指向堆数据的指针。如果你想回顾一下栈与堆的区别请参考第四章。
-
-
-
-除了数据被储存在堆上而不是栈上之外,box 没有性能损失,不过也没有很多额外的功能。他们多用于如下场景:
+除了数据被储存在堆上而不是栈上之外,box 没有性能损失。不过也没有很多额外的功能。它们多用于如下场景:
- 当有一个在编译时未知大小的类型,而又想要在需要确切大小的上下文中使用这个类型值的时候
- 当有大量数据并希望在确保数据不被拷贝的情况下转移所有权的时候
- 当希望拥有一个值并只关心它的类型是否实现了特定 trait 而不是其具体类型的时候
-我们将在本部分的余下内容中展示第一种应用场景。作为对另外两个情况更详细的说明:在第二种情况中,转移大量数据的所有权可能会花费很长的时间,因为数据在栈上进行了拷贝。为了改善这种情况下的性能,可以通过 box 将这些数据储存在堆上。接着,只有少量的指针数据在栈上被拷贝。第三种情况被称为 **trait 对象**(*trait object*),第十七章刚好有一整个部分专门讲解这个主题。所以这里所学的内容会在第十七章再次用上!
+我们会在 “box 允许创建递归类型” 部分展示第一种场景。在第二种情况中,转移大量数据的所有权可能会花费很长的时间,因为数据在栈上进行了拷贝。为了改善这种情况下的性能,可以通过 box 将这些数据储存在堆上。接着,只有少量的指针数据在栈上被拷贝。第三种情况被称为 **trait 对象**(*trait object*),第十七章刚好有一整个部分 “为使用不同类型的值而设计的 trait 对象” 专门讲解这个主题。所以这里所学的内容会在第十七章再次用上!
### 使用 `Box` 在堆上储存数据
@@ -40,62 +35,27 @@ fn main() {
将一个单独的值存放在堆上并不是很有意义,所以像示例 15-1 这样单独使用 box 并不常见。将像单个 `i32` 这样的值储存在栈上,也就是其默认存放的地方在大部分使用场景中更为合适。让我们看看一个不使用 box 时无法定义的类型的例子。
-
-
-
### box 允许创建递归类型
-
-
-
-
-
Rust 需要在编译时知道类型占用多少空间。一种无法在编译时知道大小的类型是 **递归类型**(*recursive type*),其值的一部分可以是相同类型的另一个值。这种值的嵌套理论上可以无限的进行下去,所以 Rust 不知道递归类型需要多少空间。不过 box 有一个已知的大小,所以通过在循环类型定义中插入 box,就可以创建递归类型了。
让我们探索一下 *cons list*,一个函数式编程语言中的常见类型,来展示这个(递归类型)概念。除了递归之外,我们将要定义的 cons list 类型是很直白的,所以这个例子中的概念在任何遇到更为复杂的涉及到递归类型的场景时都很实用。
-
-
-
-cons list 是一个每一项都包含两个部分的列表:当前项的值和下一项。其最后一项值包含一个叫做 `Nil` 的值并没有下一项。
-
-> #### cons list 的更多内容
->
-> *cons list* 是一个来源于 Lisp 编程语言及其方言的数据结构。在 Lisp 中,`cons` 函数(“construct function" 的缩写)利用两个参数来构造一个新的列表,他们通常是一个单独的值和另一个列表。
->
-> cons 函数的概念涉及到更通用的函数式编程术语;“将 x 与 y 连接” 通常意味着构建一个新的容器而将 x 的元素放在新容器的开头,其后则是容器 y 的元素。
->
-> cons list 通过递归调用 `cons` 函数产生。代表递归的终止条件(base case)的规范名称是 `Nil`,它宣布列表的终止。注意这不同于第六章中的 “null” 或 “nil” 的概念,他们代表无效或缺失的值。
+#### cons list 的更多内容
-注意虽然函数式编程语言经常使用 cons list,但是它并不是一个 Rust 中常见的类型。大部分在 Rust 中需要列表的时候,`Vec` 是一个更好的选择。其他更为复杂的递归数据类型 **确实** 在 Rust 的很多场景中很有用,不过通过以 cons list 作为开始,我们可以探索如何使用 box 毫不费力的定义一个递归数据类型。
+*cons list* 是一个来源于 Lisp 编程语言及其方言的数据结构。在 Lisp 中,`cons` 函数(“construct function" 的缩写)利用两个参数来构造一个新的列表,他们通常是一个单独的值和另一个列表。
+
+cons 函数的概念涉及到更通用的函数式编程术语;“将 *x* 与 *y* 连接” 通常意味着构建一个新的容器而将 *x* 的元素放在新容器的开头,其后则是容器 *y* 的元素。
-
-
+cons list 的每一项都包含两个元素:当前项的值和下一项。其最后一项值包含一个叫做 `Nil` 的值并没有下一项。cons list 通过递归调用 `cons` 函数产生。代表递归的终止条件(base case)的规范名称是 `Nil`,它宣布列表的终止。注意这不同于第六章中的 “null” 或 “nil” 的概念,他们代表无效或缺失的值。
+
+注意虽然函数式编程语言经常使用 cons list,但是它并不是一个 Rust 中常见的类型。大部分在 Rust 中需要列表的时候,`Vec` 是一个更好的选择。其他更为复杂的递归数据类型 **确实** 在 Rust 的很多场景中很有用,不过通过以 cons list 作为开始,我们可以探索如何使用 box 毫不费力的定义一个递归数据类型。
示例 15-2 包含一个 cons list 的枚举定义。注意这还不能编译因为这个类型没有已知的大小,之后我们会展示:
文件名: src/main.rs
-```rust,ignore
+```rust,ignore,does_not_compile
enum List {
Cons(i32, List),
Nil,
@@ -104,13 +64,7 @@ enum List {
示例 15-2:第一次尝试定义一个代表 `i32` 值的 cons list 数据结构的枚举
-> 注意:出于示例的需要我们选择实现一个只存放 `i32` 值的 cons list。也可以用泛型实现它,正如第十章讲到的,来定义一个可以存放任何类型值的 cons list 类型。
-
-
-
+> 注意:出于示例的需要我们选择实现一个只存放 `i32` 值的 cons list。也可以用泛型,正如第十章讲到的,来定义一个可以存放任何类型值的 cons list 类型。
使用这个 cons list 来储存列表 `1, 2, 3` 将看起来如示例 15-3 所示:
@@ -128,11 +82,11 @@ fn main() {
第一个 `Cons` 储存了 `1` 和另一个 `List` 值。这个 `List` 是另一个包含 `2` 的 `Cons` 值和下一个 `List` 值。接着又有另一个存放了 `3` 的 `Cons` 值和最后一个值为 `Nil` 的 `List`,非递归成员代表了列表的结尾。
-如果尝试编译上面的代码,会得到如示例 15-4 所示的错误:
+如果尝试编译示例 15-3 的代码,会得到如示例 15-4 所示的错误:
```text
error[E0072]: recursive type `List` has infinite size
- -->
+ --> src/main.rs:1:1
|
1 | enum List {
| ^^^^^^^^^ recursive type has infinite size
@@ -145,14 +99,6 @@ error[E0072]: recursive type `List` has infinite size
示例 15-4:尝试定义一个递归枚举时得到的错误
-
-
-
这个错误表明这个类型 “有无限的大小”。其原因是 `List` 的一个成员被定义为是递归的:它直接存放了另一个相同类型的值。这意味着 Rust 无法计算为了存放 `List` 值到底需要多少空间。让我们一点一点来看:首先了解一下 Rust 如何决定需要多少空间来存放一个非递归类型。
### 计算非递归类型的大小
@@ -170,28 +116,26 @@ enum Message {
当 Rust 需要知道要为 `Message` 值分配多少空间时,它可以检查每一个成员并发现 `Message::Quit` 并不需要任何空间,`Message::Move` 需要足够储存两个 `i32` 值的空间,依此类推。因此,`Message` 值所需的空间等于储存其最大成员的空间大小。
-与此相对当 Rust 编译器检查像示例 15-2 中的 `List` 这样的递归类型时会发生什么呢。编译器尝试计算出储存一个 `List` 枚举需要多少内存,并开始检查 `Cons` 成员,那么 `Cons` 需要的空间等于 `i32` 的大小加上 `List` 的大小。为了计算 `List` 需要多少内存,它检查其成员,从 `Cons` 成员开始。`Cons`成员储存了一个 `i32` 值和一个`List`值,这样的计算将无限进行下去,如图 15-5 所示:
+与此相对当 Rust 编译器检查像示例 15-2 中的 `List` 这样的递归类型时会发生什么呢。编译器尝试计算出储存一个 `List` 枚举需要多少内存,并开始检查 `Cons` 成员,那么 `Cons` 需要的空间等于 `i32` 的大小加上 `List` 的大小。为了计算 `List` 需要多少内存,它检查其成员,从 `Cons` 成员开始。`Cons`成员储存了一个 `i32` 值和一个`List`值,这样的计算将无限进行下去,如图 15-1 所示:
-图 15-5:一个包含无限个 `Cons` 成员的无限 `List`
+图 15-1:一个包含无限个 `Cons` 成员的无限 `List`
### 使用 `Box` 给递归类型一个已知的大小
Rust 无法计算出要为定义为递归的类型分配多少空间,所以编译器给出了示例 15-4 中的错误。这个错误也包括了有用的建议:
```text
-= help: insert indirection (e.g., a `Box`, `Rc`, or `&`) at some point to
- make `List` representable
+ = help: insert indirection (e.g., a `Box`, `Rc`, or `&`) at some point to
+ make `List` representable
```
在建议中,“indirection” 意味着不同于直接储存一个值,我们将间接的储存一个指向值的指针。
-因为 `Box` 是一个指针,我们总是知道它需要多少空间:指针的大小并不会根据其指向的数据量而改变。
-
-所以可以将 `Box` 放入 `Cons` 成员中而不是直接存放另一个 `List` 值。`Box` 会指向另一个位于堆上的 `List` 值,而不是存放在 `Cons` 成员中。从概念上讲,我们仍然有一个通过在其中 “存放” 其他列表创建的列表,不过现在实现这个概念的方式更像是一个项挨着另一项,而不是一项包含另一项。
+因为 `Box` 是一个指针,我们总是知道它需要多少空间:指针的大小并不会根据其指向的数据量而改变。这意味着可以将 `Box` 放入 `Cons` 成员中而不是直接存放另一个 `List` 值。`Box` 会指向另一个位于堆上的 `List` 值,而不是存放在 `Cons` 成员中。从概念上讲,我们仍然有一个通过在其中 “存放” 其他列表创建的列表,不过现在实现这个概念的方式更像是一个项挨着另一项,而不是一项包含另一项。
-我们可以修改示例 15-2 中 `List` 枚举的定义和示例 15-3 中对 `List` 的应用,如示例 15-6 所示,这是可以编译的:
+我们可以修改示例 15-2 中 `List` 枚举的定义和示例 15-3 中对 `List` 的应用,如示例 15-65 所示,这是可以编译的:
文件名: src/main.rs
@@ -211,21 +155,14 @@ fn main() {
}
```
-示例 15-6:为了拥有已知大小而使用 `Box` 的 `List` 定义
+示例 15-5:为了拥有已知大小而使用 `Box` 的 `List` 定义
-`Cons` 成员将会需要一个 `i32` 的大小加上储存 box 指针数据的空间。`Nil` 成员不储存值,所以它比 `Cons` 成员需要更少的空间。现在我们知道了任何 `List` 值最多需要一个 `i32` 加上 box 指针数据的大小。通过使用 box ,打破了这无限递归的连锁,这样编译器就能够计算出储存 `List` 值需要的大小了。图 15-7 展示了现在 `Cons` 成员看起来像什么:
+`Cons` 成员将会需要一个 `i32` 的大小加上储存 box 指针数据的空间。`Nil` 成员不储存值,所以它比 `Cons` 成员需要更少的空间。现在我们知道了任何 `List` 值最多需要一个 `i32` 加上 box 指针数据的大小。通过使用 box ,打破了这无限递归的连锁,这样编译器就能够计算出储存 `List` 值需要的大小了。图 15-2 展示了现在 `Cons` 成员看起来像什么:
-图 15-7:因为 `Cons` 存放一个 `Box` 所以 `List` 不是无限大小的了
+图 15-2:因为 `Cons` 存放一个 `Box` 所以 `List` 不是无限大小的了
-box 只提供了间接存储和堆分配;他们并没有任何其他特殊的功能,比如我们将会见到的其他智能指针。他们也没有这些特殊功能带来的性能损失,所以他们可以用于像 cons list 这样间接存储是唯一所需功能的场景。我们还将在第十七章看到 box 的更多应用场景。
+box 只提供了间接存储和堆分配;他们并没有任何其他特殊的功能,比如我们将会见到的其他智能指针。它们也没有这些特殊功能带来的性能损失,所以他们可以用于像 cons list 这样间接存储是唯一所需功能的场景。我们还将在第十七章看到 box 的更多应用场景。
`Box` 类型是一个智能指针,因为它实现了 `Deref` trait,它允许 `Box` 值被当作引用对待。当 `Box` 值离开作用域时,由于 `Box` 类型 `Drop` trait 的实现,box 所指向的堆数据也会被清除。让我们更详细的探索一下这两个 trait;这些 trait 在本章余下讨论的其他智能指针所提供的功能中将会更为重要。
-
-
-
diff --git a/src/ch15-02-deref.md b/src/ch15-02-deref.md
index 286ff09..ef79c4a 100644
--- a/src/ch15-02-deref.md
+++ b/src/ch15-02-deref.md
@@ -1,49 +1,18 @@
## 通过 `Deref` trait 将智能指针当作常规引用处理
-> [ch15-02-deref.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch15-02-deref.md)
+> [ch15-02-deref.md](https://github.com/rust-lang/book/blob/master/src/ch15-02-deref.md)
>
-> commit d06a6a181fd61704cbf7feb55bc61d518c6469f9
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
-实现 `Deref` trait 允许我们重载 **解引用运算符**(*dereference operator*)`*`(与乘法运算符或 glob 运算符相区别)。通过这种方式实现 `Deref` trait 可以被当作常规引用来对待,可以编写操作引用的代码并用于智能指针。
+实现 `Deref` trait 允许我们重载 **解引用运算符**(*dereference operator*)`*`(与乘法运算符或 glob 运算符相区别)。通过这种方式实现 `Deref` trait 的智能指针可以被当作常规引用来对待,可以编写操作引用的代码并用于智能指针。
-
-
+让我们首先看看解引用运算符如何处理常规引用,接着尝试定义我们自己的类似 `Box` 的类型并看看为何解引用运算符不能像引用一样工作。我们会探索如何实现 `Deref` trait 使得智能指针以类似引用的方式工作变为可能。最后,我们会讨论 Rust 的 **解引用强制多态**(*deref coercions*)功能和它是如何一同处理引用或智能指针的。
-
-
+> 我们将要构建的 `MyBox` 类型与真正的 `Box` 有一个巨大的区别:我们的版本不会在堆上储存数据。这个例子重点关注 Deref`,所以其数据实际存放在何处相比其类似指针的行为来说不算重要。
-让我们首先看看 `*` 如何处理引用,接着尝试定义我们自己的类 `Box` 类型并看看为何 `*` 不能像引用一样工作。我们会探索如何实现 `Deref` trait 使得智能指针以类似引用的方式工作变为可能。最后,我们会讨论 Rust 的 **解引用强制多态**(*deref coercions*)功能和它是如何一同处理引用或智能指针的。
+### 通过解引用运算符追踪指针的值
-### 通过 `*` 追踪指针的值
-
-
-
-
-
-
-
-常规引用是一个指针类型,一种理解指针的方式是将其看成指向储存在其他某处值的箭头。在示例 15-8 中,创建了一个 `i32` 值的引用接着使用解引用运算符来跟踪所引用的数据:
-
-
-
-
-
-
+常规引用是一个指针类型,一种理解指针的方式是将其看成指向储存在其他某处值的箭头。在示例 15-6 中,创建了一个 `i32` 值的引用接着使用解引用运算符来跟踪所引用的数据:
文件名: src/main.rs
@@ -57,7 +26,7 @@ fn main() {
}
```
-示例 15-8:使用解引用运算符来跟踪 `i32` 值的引用
+示例 15-6:使用解引用运算符来跟踪 `i32` 值的引用
变量 `x` 存放了一个 `i32` 值 `5`。`y` 等于 `x` 的一个引用。可以断言 `x` 等于 `5`。然而,如果希望对 `y` 的值做出断言,必须使用 `*y` 来追踪引用所指向的值(也就是 **解引用**)。一旦解引用了 `y`,就可以访问 `y` 所指向的整型值并可以与 `5` 做比较。
@@ -75,11 +44,11 @@ not satisfied
`{integer}`
```
-不允许比较数字的引用与数字,因为它们是不同的类型。必须使用 `*` 追踪引用所指向的值。
+不允许比较数字的引用与数字,因为它们是不同的类型。必须使用解引用运算符追踪引用所指向的值。
### 像引用一样使用 `Box`
-可以重写示例 15-8 中的代码来使用 `Box` 而不是引用,同时借引用运算符也一样能工作,如示例 15-9 所示:
+可以使用 `Box` 代替引用来重写示例 15-6 中的代码,解引用运算符也一样能工作,如示例 15-7 所示:
文件名: src/main.rs
@@ -93,15 +62,15 @@ fn main() {
}
```
-示例 15-9:在 `Box` 上使用解引用运算符
+示例 15-7:在 `Box` 上使用解引用运算符
-相比示例 15-8 唯一修改的地方就是将 `y` 设置为一个指向 `x` 值的 box 实例,而不是指向 `x` 值的引用。在最后的断言中,可以使用解引用运算符以 `y` 为引用时相同的方式追踪 box 的指针。让我们通过实现自己的 box 类型来探索 `Box` 能这么做有何特殊之处。
+示例 15-7 相比示例 15-6 唯一不同的地方就是将 `y` 设置为一个指向 `x` 值的 box 实例,而不是指向 `x` 值的引用。在最后的断言中,可以使用解引用运算符以 `y` 为引用时相同的方式追踪 box 的指针。接下来让我们通过实现自己的 box 类型来探索 `Box` 能这么做有何特殊之处。
### 自定义智能指针
为了体会默认智能指针的行为不同于引用,让我们创建一个类似于标准库提供的 `Box` 类型的智能指针。接着会学习如何增加使用解引用运算符的功能。
-从根本上说,`Box` 被定义为包含一个元素的元组结构体,所以示例 15-10 以相同的方式定义了 `MyBox` 类型。我们还定义了 `new` 函数来对应定义于 `Box` 的 `new` 函数:
+从根本上说,`Box` 被定义为包含一个元素的元组结构体,所以示例 15-8 以相同的方式定义了 `MyBox` 类型。我们还定义了 `new` 函数来对应定义于 `Box` 的 `new` 函数:
文件名: src/main.rs
@@ -115,15 +84,15 @@ impl MyBox {
}
```
-示例 15-10:定义 `MyBox` 类型
+示例 15-8:定义 `MyBox` 类型
-这里定义了一个结构体 `MyBox` 并声明了一个泛型 `T`,因为我们希望其可以存放任何类型的值。`MyBox` 是一个包含 `T` 类型元素的元组结构体。`MyBox::new` 函数获取一个 `T` 类型的参数并返回一个存放传入值的 `MyBox` 实例。
+这里定义了一个结构体 `MyBox` 并声明了一个泛型参数 `T`,因为我们希望其可以存放任何类型的值。`MyBox` 是一个包含 `T` 类型元素的元组结构体。`MyBox::new` 函数获取一个 `T` 类型的参数并返回一个存放传入值的 `MyBox` 实例。
-尝试将示例 15-9 中的代码加入示例 15-10 中并修改 `main` 使用我们定义的 `MyBox` 类型代替 `Box`。示例 15-11 中的代码不能编译,因为 Rust 不知道如何解引用 `MyBox`:
+尝试将示例 15-7 中的代码加入示例 15-8 中并修改 `main` 使用我们定义的 `MyBox` 类型代替 `Box`。示例 15-9 中的代码不能编译,因为 Rust 不知道如何解引用 `MyBox`:
文件名: src/main.rs
-```rust,ignore
+```rust,ignore,does_not_compile
fn main() {
let x = 5;
let y = MyBox::new(x);
@@ -133,23 +102,23 @@ fn main() {
}
```
-示例 15-11:尝试以使用引用和 `Box` 相同的方式使用 `MyBox`
+示例 15-9:尝试以使用引用和 `Box` 相同的方式使用 `MyBox`
得到的编译错误是:
```text
-error: type `MyBox<{integer}>` cannot be dereferenced
+error[E0614]: type `MyBox<{integer}>` cannot be dereferenced
--> src/main.rs:14:19
|
14 | assert_eq!(5, *y);
| ^^
```
-`MyBox` 类型不能解引用我们并没有为其实现这个功能。为了启用 `*` 运算符的解引用功能,可以实现 `Deref` trait。
+`MyBox` 类型不能解引用我们并没有为其实现这个功能。为了启用 `*` 运算符的解引用功能,需要实现 `Deref` trait。
-### 实现 `Deref` trait 定义如何像引用一样对待某类型
+### 通过实现 `Deref` trait 将某类型像引用一样处理
-如第十章所讨论的,为了实现 trait,需要提供 trait 所需的方法实现。`Deref` trait,由标准库提供,要求实现名为 `deref` 的方法,其借用 `self` 并返回一个内部数据的引用。示例 15-12 包含定义于 `MyBox` 之上的 `Deref` 实现:
+如第十章所讨论的,为了实现 trait,需要提供 trait 所需的方法实现。`Deref` trait,由标准库提供,要求实现名为 `deref` 的方法,其借用 `self` 并返回一个内部数据的引用。示例 15-10 包含定义于 `MyBox` 之上的 `Deref` 实现:
文件名: src/main.rs
@@ -166,49 +135,34 @@ impl Deref for MyBox {
}
```
-示例 15-12:`MyBox` 上的 `Deref` 实现
+示例 15-10:`MyBox` 上的 `Deref` 实现
`type Target = T;` 语法定义了用于此 trait 的关联类型。关联类型是一个稍有不同的定义泛型参数的方式,现在还无需过多的担心它;第十九章会详细介绍。
-
-
+`deref` 方法体中写入了 `&self.0`,这样 `deref` 返回了我希望通过 `*` 运算符访问的值的引用。示例 15-9 中的 `main` 函数中对 `MyBox` 值的 `*` 调用现在可以编译并能通过断言了!
-`deref` 方法体中写入了 `&self.0`,这样 `deref` 返回了我希望通过 `*` 运算符访问的值的引用。示例 15-11 中的 `main` 函数中对 `MyBox` 值的 `*` 调用现在可以编译并能通过断言了!
+没有 `Deref` trait 的话,编译器只会解引用 `&` 引用类型。`deref` 方法向编译器提供了获取任何实现了 `Deref` trait 的类型的值并调用这个类型的 `deref` 方法来获取一个它知道如何解引用的 `&` 引用的能力。
-没有 `Deref` trait 的话,编译器只能解引用 `&` 引用。`Deref` trait 的 `deref` 方法为编译器提供了获取任何实现了 `Deref` 的类型值的能力,为了获取其知道如何解引用的 `&` 引用编译器可以调用 `deref` 方法。
-
-当我们在示例 15-11 中输入 `*y` 时,Rust 事实上在底层运行了如下代码:
+当我们在示例 15-9 中输入 `*y` 时,Rust 事实上在底层运行了如下代码:
```rust,ignore
*(y.deref())
```
-
-
-
-Rust 将 `*` 运算符替换为 `deref` 方法调用和一个普通解引用,如此我们便无需担心是否需要调用 `deref` 方法。Rust 的这个功能让我们可以编写同时处理常规引用或实现了 `Deref` 的类型的代码。
+Rust 将 `*` 运算符替换为先调用 `deref` 方法再进行直接引用的操作,如此我们便不用担心是不是还需要手动调用 `deref` 方法了。Rust 的这个特性可以让我们写出行为一致的代码,无论是面对的是常规引用还是实现了 `Deref` 的类型。
`deref` 方法返回值的引用,以及 `*(y.deref())` 括号外边的普通解引用仍为必须的原因在于所有权。如果 `deref` 方法直接返回值而不是值的引用,其值(的所有权)将被移出 `self`。在这里以及大部分使用解引用运算符的情况下我们并不希望获取 `MyBox` 内部值的所有权。
-注意将 `*` 替换为 `deref` 调用和 `*` 调用的过程在每次使用 `*` 的时候都会发生一次。`*` 的替换并不会无限递归进行。最终的数据类型是 `i32`,它与示例 15-11 中 `assert_eq!` 的 `5` 相匹配。
+注意,每次当我们在代码中使用 `*` 时, `*` 运算符都被替换成了先调用 `deref` 方法再接着使用 `*` 解引用的操作,且只会发生一次,不会对 `*` 操作符无限递归替换,解引用出上面 `i32` 类型的值就停止了,这个值与示例 15-9 中 `assert_eq!` 的 `5` 相匹配。
### 函数和方法的隐式解引用强制多态
-
-
-
-**解引用强制多态**(*deref coercions*)是 Rust 出于方便的考虑作用于函数或方法的参数的。其将实现了 `Deref` 的类型的引用转换为 `Deref` 所能够将原始类型转换的类型的引用。解引用强制多态发生于当作为参数传递给函数或方法的特定类型的引用不同于函数或方法签名中定义参数类型的时候,这时会有一系列的 `deref` 方法调用会将提供的类型转换为参数所需的类型。
+**解引用强制多态**(*deref coercions*)是 Rust 表现在函数或方法传参上的一种便利。其将实现了 `Deref` 的类型的引用转换为原始类型通过 `Deref` 所能够转换的类型的引用。当这种特定类型的引用作为实参传递给和形参类型不同的函数或方法时,解引用强制多态将自动发生。这时会有一系列的 `deref` 方法被调用,把我们提供的类型转换成了参数所需的类型。
解引用强制多态的加入使得 Rust 程序员编写函数和方法调用时无需增加过多显式使用 `&` 和 `*` 的引用和解引用。这个功能也使得我们可以编写更多同时作用于引用或智能指针的代码。
-作为展示解引用强制多态的实例,让我们使用示例 15-10 中定义的 `MyBox`,以及示例 15-12 中增加的 `Deref` 实现。示例 15-13 展示了一个有着字符串 slice 参数的函数定义:
+作为展示解引用强制多态的实例,让我们使用示例 15-8 中定义的 `MyBox`,以及示例 15-10 中增加的 `Deref` 实现。示例 15-11 展示了一个有着字符串 slice 参数的函数定义:
文件名: src/main.rs
@@ -218,9 +172,9 @@ fn hello(name: &str) {
}
```
-示例 15-13:`hello` 函数有着 `&str` 类型的参数 `name`
+示例 15-11:`hello` 函数有着 `&str` 类型的参数 `name`
-可以使用字符串 slice 作为参数调用 `hello` 函数,比如 `hello("Rust");`。解引用强制多态使得用 `MyBox` 类型值的引用调用 `hello` 成为可能,如示例 15-14 所示:
+可以使用字符串 slice 作为参数调用 `hello` 函数,比如 `hello("Rust");`。解引用强制多态使得用 `MyBox` 类型值的引用调用 `hello` 成为可能,如示例 15-12 所示:
文件名: src/main.rs
@@ -253,11 +207,11 @@ fn main() {
}
```
-示例 15-14:因为解引用强制多态,使用 `MyBox` 的引用调用 `hello` 是可行的
+示例 15-12:因为解引用强制多态,使用 `MyBox` 的引用调用 `hello` 是可行的
-这里使用 `&m` 调用 `hello` 函数,其为 `MyBox` 值的引用。因为示例 15-12 中在 `MyBox` 上实现了 `Deref` trait,Rust 可以通过 `deref` 调用将 `&MyBox` 变为 `&String`。标准库中提供了 `String` 上的 `Deref` 实现,其会返回字符串 slice,这可以在 `Deref` 的 API 文档中看到。Rust 再次调用 `deref` 将 `&String` 变为 `&str`,这就符合 `hello` 函数的定义了。
+这里使用 `&m` 调用 `hello` 函数,其为 `MyBox` 值的引用。因为示例 15-10 中在 `MyBox` 上实现了 `Deref` trait,Rust 可以通过 `deref` 调用将 `&MyBox` 变为 `&String`。标准库中提供了 `String` 上的 `Deref` 实现,其会返回字符串 slice,这可以在 `Deref` 的 API 文档中看到。Rust 再次调用 `deref` 将 `&String` 变为 `&str`,这就符合 `hello` 函数的定义了。
-如果 Rust 没有实现解引用强制多态,为了使用 `&MyBox` 类型的值调用 `hello`,则不得不编写示例 15-15 中的代码来代替示例 15-14:
+如果 Rust 没有实现解引用强制多态,为了使用 `&MyBox` 类型的值调用 `hello`,则不得不编写示例 15-13 中的代码来代替示例 15-12:
文件名: src/main.rs
@@ -290,7 +244,7 @@ fn main() {
}
```
-示例 15-15:如果 Rust 没有解引用强制多态则必须编写的代码
+示例 15-13:如果 Rust 没有解引用强制多态则必须编写的代码
`(*m)` 将 `MyBox` 解引用为 `String`。接着 `&` 和 `[..]` 获取了整个 `String` 的字符串 slice 来匹配 `hello` 的签名。没有解引用强制多态所有这些符号混在一起将更难以读写和理解。解引用强制多态使得 Rust 自动的帮我们处理这些转换。
@@ -298,33 +252,14 @@ fn main() {
### 解引用强制多态如何与可变性交互
-
-
-
类似于如何使用 `Deref` trait 重载不可变引用的 `*` 运算符,Rust 提供了 `DerefMut` trait 用于重载可变引用的 `*` 运算符。
Rust 在发现类型和 trait 实现满足三种情况时会进行解引用强制多态:
-
-
-
* 当 `T: Deref` 时从 `&T` 到 `&U`。
* 当 `T: DerefMut` 时从 `&mut T` 到 `&mut U`。
* 当 `T: Deref` 时从 `&mut T` 到 `&U`。
头两个情况除了可变性之外是相同的:第一种情况表明如果有一个 `&T`,而 `T` 实现了返回 `U` 类型的 `Deref`,则可以直接得到 `&U`。第二种情况表明对于可变引用也有着相同的行为。
-最后一个情况有些微妙:Rust 也会将可变引用强转为不可变引用。但是反之是 **不可能** 的:不可变引用永远也不能强转为可变引用。因为根据借用规则,如果有一个可变引用,其必须是这些数据的唯一引用(否则程序将无法编译)。将一个可变引用转换为不可变引用永远也不会打破借用规则。将不可变引用转换为可变引用则需要数据只能有一个不可变引用,而借用规则无法保证这一点。因此,Rust 无法假设将不可变引用转换为可变引用是可能的。
-
-
-
+第三个情况有些微妙:Rust 也会将可变引用强转为不可变引用。但是反之是 **不可能** 的:不可变引用永远也不能强转为可变引用。因为根据借用规则,如果有一个可变引用,其必须是这些数据的唯一引用(否则程序将无法编译)。将一个可变引用转换为不可变引用永远也不会打破借用规则。将不可变引用转换为可变引用则需要数据只能有一个不可变引用,而借用规则无法保证这一点。因此,Rust 无法假设将不可变引用转换为可变引用是可能的。
diff --git a/src/ch15-03-drop.md b/src/ch15-03-drop.md
index ba62d57..633ff3e 100644
--- a/src/ch15-03-drop.md
+++ b/src/ch15-03-drop.md
@@ -1,18 +1,16 @@
## 使用 `Drop` Trait 运行清理代码
-> [ch15-03-drop.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch15-03-drop.md)
+> [ch15-03-drop.md](https://github.com/rust-lang/book/blob/master/src/ch15-03-drop.md)
>
-> commit 6060440d67759b7c8627b4d97cb69576057f5fa6
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
-对于智能指针模式来说另一个重要的 trait 是 `Drop`。`Drop` 允许我们在值要离开作用域时执行一些代码。可以为任何类型提供 `Drop` trait 的实现,同时所指定的代码被用于释放类似于文件或网络连接的资源。我们在智能指针上下文中讨论 `Drop` 是因为其功能几乎总是用于实现智能指针。例如,`Box` 自定义了 `Drop` 用来释放 box 所指向的堆空间。
+对于智能指针模式来说第二个重要的 trait 是 `Drop`,其允许我们在值要离开作用域时执行一些代码。可以为任何类型提供 `Drop` trait 的实现,同时所指定的代码被用于释放类似于文件或网络连接的资源。我们在智能指针上下文中讨论 `Drop` 是因为其功能几乎总是用于实现智能指针。例如,`Box` 自定义了 `Drop` 用来释放 box 所指向的堆空间。
-在其他一些语言中,我们不得不记住在每次使用完智能指针实例后调用清理内存或资源的代码。如果忘记的话,运行代码的系统可能会因为负荷过重而崩溃。在 Rust 中,可以指定一些代码应该在值离开作用域时被执行,而编译器会自动插入这些代码。于是我们就不需要在程序中到处编写在实例结束时清理这些变量的代码——而且还不会泄露资源了。
-
-这意味着无需记住在所有处理完这些类型实例后调用清理代码,而仍然不会泄露资源!
+在其他一些语言中,我们不得不记住在每次使用完智能指针实例后调用清理内存或资源的代码。如果忘记的话,运行代码的系统可能会因为负荷过重而崩溃。在 Rust 中,可以指定一些代码应该在值离开作用域时被执行,而编译器会自动插入这些代码。于是我们就不需要在程序中到处编写在实例结束时清理这些变量的代码 —— 而且还不会泄露资源。
指定在值离开作用域时应该执行的代码的方式是实现 `Drop` trait。`Drop` trait 要求实现一个叫做 `drop` 的方法,它获取一个 `self` 的可变引用。为了能够看出 Rust 何时调用 `drop`,让我们暂时使用 `println!` 语句实现 `drop`。
-示例 15-16 展示了唯一定制功能就是当其实例离开作用域时打印出 `Dropping CustomSmartPointer!` 的结构体 `CustomSmartPointer`。这会演示 Rust 何时运行 `drop` 函数:
+示例 15-14 展示了唯一定制功能就是当其实例离开作用域时打印出 `Dropping CustomSmartPointer!` 的结构体 `CustomSmartPointer`。这会演示 Rust 何时运行 `drop` 函数:
文件名: src/main.rs
@@ -52,13 +50,13 @@ Dropping CustomSmartPointer with data `my stuff`!
#### 通过 `std::mem::drop` 提早丢弃值
-不幸的是,我们并不能直截了当的禁用 `drop` 这个功能。通常也不需要禁用 `drop` ;整个 `Drop` trait 存在的意义在于其是自动处理的。然而,有时你可能需要提早清理某个值。一个例子是当使用智能指针管理锁时;你可能希望强制运行 `drop` 方法来释放锁以便作用域中的其他代码可以获取锁。Rust 并不允许我们主动调用 `Drop` trait 的 `drop` 方法;当我们希望在作用域结束之前就释放变量的话,我们应该使用的是由标准库提供的 `std::mem::drop`。
+不幸的是,我们并不能直截了当的禁用 `drop` 这个功能。通常也不需要禁用 `drop` ;整个 `Drop` trait 存在的意义在于其是自动处理的。然而,有时你可能需要提早清理某个值。一个例子是当使用智能指针管理锁时;你可能希望强制运行 `drop` 方法来释放锁以便作用域中的其他代码可以获取锁。Rust 并不允许我们主动调用 `Drop` trait 的 `drop` 方法;当我们希望在作用域结束之前就强制释放变量的话,我们应该使用的是由标准库提供的 `std::mem::drop`。
如果我们像是示例 15-14 那样尝试调用 `Drop` trait 的 `drop` 方法,就会得到像示例 15-15 那样的编译错误:
文件名: src/main.rs
-```rust,ignore
+```rust,ignore,does_not_compile
fn main() {
let c = CustomSmartPointer { data: String::from("some data") };
println!("CustomSmartPointer created.");
@@ -83,7 +81,7 @@ error[E0040]: explicit use of destructor method
Rust 不允许我们显式调用 `drop` 因为 Rust 仍然会在 `main` 的结尾对值自动调用 `drop`,这会导致一个 **double free** 错误,因为 Rust 会尝试清理相同的值两次。
-因为不能禁用当值离开作用域时自动插入的 `drop`,并且不能显示调用 `drop`,如果我们需要提早清理值,可以使用 `std::mem::drop` 函数。
+因为不能禁用当值离开作用域时自动插入的 `drop`,并且不能显示调用 `drop`,如果我们需要强制提早清理值,可以使用 `std::mem::drop` 函数。
`std::mem::drop` 函数不同于 `Drop` trait 中的 `drop` 方法。可以通过传递希望提早强制丢弃的值作为参数。`std::mem::drop` 位于 prelude,所以我们可以修改示例 15-15 中的 `main` 来调用 `drop` 函数如示例 15-16 所示:
@@ -124,6 +122,4 @@ CustomSmartPointer dropped before the end of main.
我们也无需担心意外的清理掉仍在使用的值,这会造成编译器错误:所有权系统确保引用总是有效的,也会确保 `drop` 只会在值不再被使用时被调用一次。
-使用 `Drop` trait 实现指定的代码在很多方面都使得清理值变得方便和安全:比如可以使用它来创建我们自己的内存分配器!通过`Drop` trait 和 Rust 所有权系统,就无需担心之后清理代码,因为 Rust 会自动考虑这些问题。如果代码在值仍被使用时就清理它会出现编译错误,因为所有权系统确保了引用总是有效的,这也就保证了`drop`只会在值不再被使用时被调用一次。
-
现在我们学习了 `Box` 和一些智能指针的特性,让我们聊聊一些其他标准库中定义的智能指针。
\ No newline at end of file
diff --git a/src/ch15-04-rc.md b/src/ch15-04-rc.md
index 5f86fd5..f885a7a 100644
--- a/src/ch15-04-rc.md
+++ b/src/ch15-04-rc.md
@@ -1,15 +1,11 @@
## `Rc` 引用计数智能指针
-> [ch15-04-rc.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch15-04-rc.md)
+> [ch15-04-rc.md](https://github.com/rust-lang/book/blob/master/src/ch15-04-rc.md)
>
-> commit 071b97540bca12fd416d2ea7a2daa5d3e9c74400
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
大部分情况下所有权是非常明确的:可以准确的知道哪个变量拥有某个值。然而,有些情况单个值可能会有多个所有者。例如,在图数据结构中,多个边可能指向相同的结点,而这个结点从概念上讲为所有指向它的边所拥有。结点直到没有任何边指向它之前都不应该被清理。
-
-
-
为了启用多所有权,Rust 有一个叫做 `Rc` 的类型。其名称为 **引用计数**(*reference counting*)的缩写。引用计数意味着记录一个值引用的数量来知晓这个值是否仍在被使用。如果某个值有零个引用,就代表没有任何有效引用并可以被清理。
可以将其想象为客厅中的电视。当一个人进来看电视时,他打开电视。其他人也可以进来看电视。当最后一个人离开房间时,他关掉电视因为它不再被使用了。如果某人在其他人还在看的时候就关掉了电视,正在看电视的人肯定会抓狂的!
@@ -20,19 +16,19 @@
### 使用 `Rc` 共享数据
-让我们回到示例 15-6 中使用 `Box` 定义 cons list 的例子。这一次,我们希望创建两个共享第三个列表所有权的列表,其概念将会看起来如图 15-19 所示:
+让我们回到示例 15-5 中使用 `Box` 定义 cons list 的例子。这一次,我们希望创建两个共享第三个列表所有权的列表,其概念将会看起来如图 15-3 所示:
-图 15-19: 两个列表, `b` 和 `c`, 共享第三个列表 `a` 的所有权
+图 15-3: 两个列表, `b` 和 `c`, 共享第三个列表 `a` 的所有权
列表 `a` 包含 5 之后是 10,之后是另两个列表:`b` 从 3 开始而 `c` 从 4 开始。`b` 和 `c` 会接上包含 5 和 10 的列表 `a`。换句话说,这两个列表会尝试共享第一个列表所包含的 5 和 10。
-尝试使用 `Box` 定义的 `List` 并实现不能工作,如示例 15-20 所示:
+尝试使用 `Box` 定义的 `List` 并实现不能工作,如示例 15-17 所示:
文件名: src/main.rs
-```rust,ignore
+```rust,ignore,does_not_compile
enum List {
Cons(i32, Box),
Nil,
@@ -49,7 +45,7 @@ fn main() {
}
```
-示例 15-20: 展示不能用两个 `Box` 的列表尝试共享第三个列表的所有权
+示例 15-17: 展示不能用两个 `Box` 的列表尝试共享第三个列表的所有权
编译会得出如下错误:
@@ -62,21 +58,15 @@ error[E0382]: use of moved value: `a`
13 | let c = Cons(4, Box::new(a));
| ^ value used here after move
|
- = note: move occurs because `a` has type `List`, which does not
- implement the `Copy` trait
+ = note: move occurs because `a` has type `List`, which does not implement
+ the `Copy` trait
```
`Cons` 成员拥有其储存的数据,所以当创建 `b` 列表时,`a` 被移动进了 `b` 这样 `b` 就拥有了 `a`。接着当再次尝使用 `a` 创建 `c` 时,这不被允许因为 `a` 的所有权已经被移动。
可以改变 `Cons` 的定义来存放一个引用,不过接着必须指定生命周期参数。通过指定生命周期参数,表明列表中的每一个元素都至少与列表本身存在的一样久。例如,借用检查器不会允许 `let a = Cons(10, &Nil);` 编译,因为临时值 `Nil` 会在 `a` 获取其引用之前就被丢弃了。
-相反,我们修改 `List` 的定义为使用 `Rc` 代替 `Box`,如列表 15-21 所示。现在每一个 `Cons` 变量都包含一个值和一个指向 `List` 的 `Rc`。当创建 `b` 时,不同于获取 `a` 的所有权,这里会克隆 `a` 所包含的 `Rc`,这会将引用计数从 1 增加到 2 并允许 `a` 和 `b` 共享 `Rc` 中数据的所有权。创建 `c` 时也会克隆 `a`,这会将引用计数从 2 增加为 3。每次调用 `Rc::clone`,`Rc` 中数据的引用计数都会增加,直到有零个引用之前其数据都不会被清理:
-
-
-
+相反,我们修改 `List` 的定义为使用 `Rc` 代替 `Box`,如列表 15-18 所示。现在每一个 `Cons` 变量都包含一个值和一个指向 `List` 的 `Rc`。当创建 `b` 时,不同于获取 `a` 的所有权,这里会克隆 `a` 所包含的 `Rc`,这会将引用计数从 1 增加到 2 并允许 `a` 和 `b` 共享 `Rc` 中数据的所有权。创建 `c` 时也会克隆 `a`,这会将引用计数从 2 增加为 3。每次调用 `Rc::clone`,`Rc` 中数据的引用计数都会增加,直到有零个引用之前其数据都不会被清理。
文件名: src/main.rs
@@ -96,30 +86,17 @@ fn main() {
}
```
-示例 15-21: 使用 `Rc` 定义的 `List`
+示例 15-18: 使用 `Rc` 定义的 `List`
-需要为 `Rc` 增加`use`语句因为它不在 prelude 中。在 `main` 中创建了存放 5 和 10 的列表并将其存放在 `a` 的新的 `Rc` 中。接着当创建 `b` 和 `c` 时,调用 `Rc::clone` 函数并传递 `a` 中 `Rc` 的引用作为参数。
+需要使用 `use` 语句将 `Rc` 引入作用域因为它不在 prelude 中。在 `main` 中创建了存放 5 和 10 的列表并将其存放在 `a` 的新的 `Rc` 中。接着当创建 `b` 和 `c` 时,调用 `Rc::clone` 函数并传递 `a` 中 `Rc` 的引用作为参数。
-也可以调用 `a.clone()` 而不是 `Rc::clone(&a)`,不过在这里 Rust 的习惯是使用 `Rc::clone`。`Rc::clone` 的实现并不像大部分类型的 `clone` 实现那样对所有数据进行深拷贝。`Rc::clone` 只会增加引用计数,这并不会花费多少时间。深拷贝可能会花费很长时间,所以通过使用 `Rc::clone` 进行引用计数,可以明显的区别可能会对运行时性能有巨大影响的深拷贝和不分配内存的对运行时性能影响相对较小的增加引用计数拷贝。
+也可以调用 `a.clone()` 而不是 `Rc::clone(&a)`,不过在这里 Rust 的习惯是使用 `Rc::clone`。`Rc::clone` 的实现并不像大部分类型的 `clone` 实现那样对所有数据进行深拷贝。`Rc::clone` 只会增加引用计数,这并不会花费多少时间。深拷贝可能会花费很长时间。通过使用 `Rc::clone` 进行引用计数,可以明显的区别深拷贝类的克隆和增加引用计数类的克隆。当查找代码中的性能问题时,只需考虑神拷贝类克隆而无需考虑 `Rc::clone` 调用。
### 克隆 `Rc` 会增加引用计数
-让我们修改示例 15-21 的代码以便观察创建和丢弃 `a` 中 `Rc` 的引用时引用计数的变化。
-
-
-
+让我们修改示例 15-18 的代码以便观察创建和丢弃 `a` 中 `Rc` 的引用时引用计数的变化。
-在示例 15-22 中,修改了 `main` 以便将列表 `c` 置于内部作用域中,这样就可以观察当 `c` 离开作用域时引用计数如何变化。在程序中每个引用计数变化的点,会打印出引用计数,其值可以通过调用 `Rc::strong_count` 函数获得。在本章稍后的部分讨论避免引用循环时会解释为何这个函数叫做 `strong_count` 而不是 `count`。
-
-
-
+在示例 15-19 中,修改了 `main` 以便将列表 `c` 置于内部作用域中,这样就可以观察当 `c` 离开作用域时引用计数如何变化。
文件名: src/main.rs
@@ -145,9 +122,11 @@ fn main() {
}
```
-示例 15-22:打印出引用计数
+示例 15-19:打印出引用计数
+
+在程序中每个引用计数变化的点,会打印出引用计数,其值可以通过调用 `Rc::strong_count` 函数获得。这个函数叫做 `strong_count` 而不是 `count` 是因为 `Rc` 也有 `weak_count`;在 “避免引用循环” 部分会讲解 `weak_count` 的用途。
-这会打印出:
+这段代码会打印出:
```text
count after creating a = 1
@@ -156,11 +135,7 @@ count after creating c = 3
count after c goes out of scope = 2
```
-
-
-
-我们能够看到 `a` 中 `Rc` 的初始引用计数为一,接着每次调用 `clone`,计数会增加一。当 `c` 离开作用域时,计数减一。不必像调用 `Rc::clone` 增加引用计数那样调用一个函数来减少计数;`Drop` trait 的实现当 `Rc` 值离开作用域时自动减少引用计数。
+我们能够看到 `a` 中 `Rc` 的初始引用计数为一,接着每次调用 `clone`,计数会增加一。当 `c` 离开作用域时,计数减一。不必像调用 `Rc::clone` 增加引用计数那样调用一个函数来减少计数;`Drop` trait 的实现当 `Rc` 值离开作用域时自动减少引用计数。
从这个例子我们所不能看到的是在 `main` 的结尾当 `b` 然后是 `a` 离开作用域时,此处计数会是 0,同时 `Rc` 被完全清理。使用 `Rc` 允许一个值有多个所有者,引用计数则确保只要任何所有者依然存在其值也保持有效。
diff --git a/src/ch15-05-interior-mutability.md b/src/ch15-05-interior-mutability.md
index 05354a9..d9b7626 100644
--- a/src/ch15-05-interior-mutability.md
+++ b/src/ch15-05-interior-mutability.md
@@ -1,24 +1,12 @@
## `RefCell` 和内部可变性模式
-> [ch15-05-interior-mutability.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch15-05-interior-mutability.md)
+> [ch15-05-interior-mutability.md](https://github.com/rust-lang/book/blob/master/src/ch15-05-interior-mutability.md)
>
-> commit 54169ef43f57847913ebec7e021c1267663a5d12
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
-
-
+
-
-
-
-**内部可变性**(*Interior mutability*)是 Rust 中的一个设计模式,它允许你即使在有不可变引用时改变数据,这通常是借用规则所不允许的。为此,该模式在数据结构中使用 `unsafe` 代码来模糊 Rust 通常的可变性和借用规则。我们还未讲到不安全代码;第十九章会学习它们。当可以确保代码在运行时会遵守借用规则,即使编译器不能保证的情况,可以选择使用那些运用内部可变性模式的类型。所涉及的 `unsafe` 代码将被封装进安全的 API 中,而外部类型仍然是不可变的。
+**内部可变性**(*Interior mutability*)是 Rust 中的一个设计模式,它允许你即使在有不可变引用时改变数据,这通常是借用规则所不允许的。为了改变数据,该模式在数据结构中使用 `unsafe` 代码来模糊 Rust 通常的可变性和借用规则。我们还未讲到不安全代码;第十九章会学习它们。当可以确保代码在运行时会遵守借用规则,即使编译器不能保证的情况,可以选择使用那些运用内部可变性模式的类型。所涉及的 `unsafe` 代码将被封装进安全的 API 中,而外部类型仍然是不可变的。
让我们通过遵循内部可变性模式的 `RefCell` 类型来开始探索。
@@ -26,46 +14,32 @@ pattern. /Carol -->
不同于 `Rc`,`RefCell` 代表其数据的唯一的所有权。那么是什么让 `RefCell` 不同于像 `Box` 这样的类型呢?回忆一下第四章所学的借用规则:
-1. 在任意给定时间,**只能** 拥有如下中的一个:
- * 一个可变引用。
- * 任意数量的不可变引用。
+1. 在任意给定时间,只能拥有一个可变引用或任意数量的不可变引用 **之一**(而不是全部)。
2. 引用必须总是有效的。
-对于引用和 `Box`,借用规则的不可变性作用于编译时。对于 `RefCell`,这些不可变性作用于 **运行时**。对于引用,如果违反这些规则,会得到一个编译错误。而对于`RefCell`,违反这些规则会 `panic!`。
-
-
-
+对于引用和 `Box`,借用规则的不可变性作用于编译时。对于 `RefCell`,这些不可变性作用于 **运行时**。对于引用,如果违反这些规则,会得到一个编译错误。而对于 `RefCell`,如果违反这些规则程序会 panic 并退出。
-在编译时检查借用规则的好处是这些错误将在开发过程的早期被捕获同时对没有运行时性能影响,因为所有的分析都提前完成了。为此,在编译时检查借用规则是大部分情况的最佳选择,这也正是其为何是 Rust 的默认行为。
+在编译时检查借用规则的优势是这些错误将在开发过程的早期被捕获同时对没有运行时性能影响,因为所有的分析都提前完成了。为此,在编译时检查借用规则是大部分情况的最佳选择,这也正是其为何是 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` 正是用于当你确信代码遵守借用规则,而编译器不能理解和确定的时候。
+因为一些分析是不可能的,如果 Rust 编译器不能通过所有权规则编译,它可能会拒绝一个正确的程序;从这种角度考虑它是保守的。如果 Rust 接受不正确的程序,那么用户也就不会相信 Rust 所做的保证了。然而,如果 Rust 拒绝正确的程序,虽然会给程序员带来不便,但不会带来灾难。`RefCell` 正是用于当你确信代码遵守借用规则,而编译器不能理解和确定的时候。
类似于 `Rc`,`RefCell` 只能用于单线程场景。如果尝试在多线程上下文中使用`RefCell`,会得到一个编译错误。第十六章会介绍如何在多线程程序中使用 `RefCell` 的功能。
-
-
-
如下为选择 `Box`,`Rc` 或 `RefCell` 的理由:
-- `Rc` 允许相同数据有多个所有者;`Box` 和 `RefCell` 有单一所有者。
-- `Box` 允许在编译时执行不可变(或可变)借用检查;`Rc`仅允许在编译时执行不可变借用检查;`RefCell` 允许在运行时执行不可变(或可变)借用检查。
-- 因为 `RefCell` 允许在运行时执行可变借用检查,所以我们可以在即便 `RefCell` 自身是不可变的情况下修改其内部的值。
+* `Rc` 允许相同数据有多个所有者;`Box` 和 `RefCell` 有单一所有者。
+* `Box