diff --git a/README.md b/README.md
index bb6ebe3..876eb58 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,10 @@
-# Rust 程序设计语言(第二版) 简体中文版
+# Rust 程序设计语言(第二版 & 2018 edition) 简体中文版
[![Build Status](https://travis-ci.org/KaiserY/trpl-zh-cn.svg?branch=master)](https://travis-ci.org/KaiserY/trpl-zh-cn)
## 状态
-目前正向 2018 edtion 过渡,目前官方仓库状已经不再分版本提供源码了,故本仓库准备与官方保持一致。已更新到第十七章。
+2018 edition 的翻译迁移已基本完成,欢迎阅读学习!
PS:
@@ -17,15 +17,15 @@ PS:
### 构建
-你可以将本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
@@ -33,7 +33,7 @@ vuepress build ./src
### 文档撰写
-vuepress会启动一个本地服务器,并在浏览器对你保存的文档进行实时热更新。
+vuepress 会启动一个本地服务器,并在浏览器对你保存的文档进行实时热更新。
```bash
vuepress dev ./src
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 90bd032..0c0e228 100644
--- a/src/SUMMARY.md
+++ b/src/SUMMARY.md
@@ -115,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)
@@ -125,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..9c0d95f
--- /dev/null
+++ b/src/appendix-04-useful-development-tools.md
@@ -0,0 +1,5 @@
+## 附录 E:实用开发工具
+
+> [appendix-04-useful-development-tools.md](https://github.com/rust-lang/book/blob/master/src/appendix-04-useful-development-tools.md)
+>
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
\ 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..51bb588
--- /dev/null
+++ b/src/appendix-05-editions.md
@@ -0,0 +1,5 @@
+## 附录 E:版本
+
+> [appendix-05-editions.md](https://github.com/rust-lang/book/blob/master/src/appendix-05-editions.md)
+>
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
\ 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..fc48b22 100644
--- a/src/appendix-07-nightly-rust.md
+++ b/src/appendix-07-nightly-rust.md
@@ -1 +1,5 @@
-## 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
\ No newline at end of file
diff --git a/src/ch19-04-advanced-types.md b/src/ch19-04-advanced-types.md
index 756befa..7a7c3f1 100644
--- a/src/ch19-04-advanced-types.md
+++ b/src/ch19-04-advanced-types.md
@@ -1,20 +1,18 @@
## 高级类型
-> [ch19-04-advanced-types.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch19-04-advanced-types.md)
+> [ch19-04-advanced-types.md](https://github.com/rust-lang/book/blob/master/src/ch19-04-advanced-types.md)
>
-> commit 9d5b9a573daf5fa0c98b3a3005badcea4a0a5211
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
Rust 的类型系统有一些我们曾经提到但没有讨论过的功能。首先我们从一个关于为什么 newtype 与类型一样有用的更宽泛的讨论开始。接着会转向类型别名(type aliases),一个类似于 newtype 但有着稍微不同的语义的功能。我们还会讨论 `!` 类型和动态大小类型。
-### 为了类型安全和抽象而使用 newtype 模式
+> 这一部分假设你已经阅读了之前的 “newtype 模式用于在外部类型上实现外部 trait” 部分。
-> 这一部分假设你已经阅读了 “高级 trait” 部分的 newtype 模式相关内容。
+### 为了类型安全和抽象而使用 newtype 模式
newtype 模式可以用于一些其他我们还未讨论的功能,包括静态的确保某值不被混淆,和用来表示一个值的单元。实际上示例 19-23 中已经有一个这样的例子:`Millimeters` 和 `Meters` 结构体都在 newtype 中封装了 `u32` 值。如果编写了一个有 `Millimeters` 类型参数的函数,不小心使用 `Meters` 或普通的 `u32` 值来调用该函数的程序是不能编译的。
-另一个 newtype 模式的应用在于抽象掉一些类型的实现细节:例如,封装类型可以暴露出与直接使用其内部私有类型时所不同的 API,以便限制其功能。
-
-
+另一个 newtype 模式的应用在于抽象掉一些类型的实现细节:例如,封装类型可以暴露出与直接使用其内部私有类型时所不同的公有 API,以便限制其功能。
newtype 也可以隐藏其内部的泛型类型。例如,可以提供一个封装了 `HashMap` 的 `People` 类型,用来储存人名以及相应的 ID。使用 `People` 的代码只需与提供的公有 API 交互即可,比如向 `People` 集合增加名字字符串的方法,这样这些代码就无需知道在内部我们将一个 `i32` ID 赋予了这个名字了。newtype 模式是一种实现第十七章 “封装隐藏了实现细节” 部分所讨论的隐藏实现细节的封装的轻量级方法。
@@ -42,19 +40,19 @@ println!("x + y = {}", x + y);
类型别名的主要用途是减少重复。例如,可能会有这样很长的类型:
```rust,ignore
-Box
+Box
```
在函数签名或类型注解中每次都书写这个类型将是枯燥且易于出错的。想象一下如示例 19-32 这样全是如此代码的项目:
```rust
-let f: Box = Box::new(|| println!("hi"));
+let f: Box = Box::new(|| println!("hi"));
-fn takes_long_type(f: Box) {
+fn takes_long_type(f: Box) {
// --snip--
}
-fn returns_long_type() -> Box {
+fn returns_long_type() -> Box {
// --snip--
# Box::new(|| ())
}
@@ -65,7 +63,7 @@ fn returns_long_type() -> Box {
类型别名通过减少项目中重复代码的数量来使其更加易于控制。这里我们为这个冗长的类型引入了一个叫做 `Thunk` 的别名,这样就可以如示例 19-33 所示将所有使用这个类型的地方替换为更短的 `Thunk`:
```rust
-type Thunk = Box;
+type Thunk = Box;
let f: Thunk = Box::new(|| println!("hi"));
@@ -104,7 +102,7 @@ pub trait Write {
type Result = Result;
```
-因为这位于 `std::io` 中,可用的完全限定的别名是 `std::io::Result`;也就是说,`Result` 中 `E` 放入了 `std::io::Error`。`Write` trait 中的函数最终看起来像这样:
+因为这位于 `std::io` 中,可用的完全限定的别名是 `std::io::Result` —— 也就是说,`Result` 中 `E` 放入了 `std::io::Error`。`Write` trait 中的函数最终看起来像这样:
```rust,ignore
pub trait Write {
@@ -118,7 +116,7 @@ pub trait Write {
类型别名在两个方面有帮助:易于编写 **并** 在整个 `std::io` 中提供了一致的接口。因为这是一个别名,它只是另一个 `Result`,这意味着可以在其上使用 `Result` 的任何方法,以及像 `?` 这样的特殊语法。
-### 从不返回的 `!`,never type
+### 从不返回的 never type
Rust 有一个叫做 `!` 的特殊类型。在类型理论术语中,它被称为 *empty type*,因为它没有值。我们更倾向于称之为 *never type*。这个名字描述了它的作用:在函数从不返回的时候充当返回值。例如:
@@ -128,10 +126,9 @@ fn bar() -> ! {
}
```
-这读 “函数 `bar` 从不返回”,而从不返回的函数被称为 **发散函数**(*diverging functions*)。不能创建 `!` 类型的值,所以 `bar` 也不可能返回。
-
+这读 “函数 `bar` 从不返回”,而从不返回的函数被称为 **发散函数**(*diverging functions*)。不能创建 `!` 类型的值,所以 `bar` 也不可能返回值。
-不过一个不能创建值的类型有什么用呢?如果你回想一下第二章,曾经有一些看起来像这样的代码,如示例 19-34 所重现的:
+不过一个不能创建值的类型有什么用呢?如果你回想一下示例 2-5 中的代码,曾经有一些看起来像这样的代码,如示例 19-34 所重现的:
```rust
# let guess = "3";
@@ -148,27 +145,19 @@ let guess: u32 = match guess.trim().parse() {
当时我们忽略了代码中的一些细节。在第六章 “`match` 控制流运算符” 部分,我们学习了 `match` 的分支必须返回相同的类型。如下代码不能工作:
-```rust,ignore
-let guess = match guess.trim().parse() {
+```rust,ignore,does_not_compile
+let guess = match guess.trim().parse() {
Ok(_) => 5,
Err(_) => "hello",
}
```
-这里的 `guess` 必须既是整型也是字符串,而 Rust 要求 `guess` 只能是一个类型。那么 `continue` 返回了什么呢?为什么示例 19-34 中会允许一个分支返回 `u32` 而另一个分支却以 `continue` 结束呢?
+这里的 `guess` 必须既是整型 **也是** 字符串,而 Rust 要求 `guess` 只能是一个类型。那么 `continue` 返回了什么呢?为什么示例 19-34 中会允许一个分支返回 `u32` 而另一个分支却以 `continue` 结束呢?
正如你可能猜到的,`continue` 的值是 `!`。也就是说,当 Rust 要计算 `guess` 的类型时,它查看这两个分支。前者是 `u32` 值,而后者是 `!` 值。因为 `!` 并没有一个值,Rust 决定 `guess` 的类型是 `u32`。
描述 `!` 的行为的正式方式是 never type 可以强转为任何其他类型。允许 `match` 的分支以 `continue` 结束是因为 `continue` 并不真正返回一个值;相反它把控制权交回上层循环,所以在 `Err` 的情况,事实上并未对 `guess` 赋值。
-
-
-
never type 的另一个用途是 `panic!`。还记得 `Option` 上的 `unwrap` 函数吗?它产生一个值或 panic。这里是它的定义:
```rust,ignore
@@ -182,7 +171,7 @@ impl Option {
}
```
-这里与示例 19-34 中的 `match` 发生了相同的情况:我们知道 `val` 是 `T` 类型,`panic!` 是 `!` 类型,所以整个 `match` 表达式的结果是 `T` 类型。这能工作是因为 `panic!` 并不产生一个值;它会终止程序。对于 `None` 的情况,`unwrap` 并不返回一个值,所以这些代码是有效。
+这里与示例 19-34 中的 `match` 发生了相同的情况:Rust 知道 `val` 是 `T` 类型,`panic!` 是 `!` 类型,所以整个 `match` 表达式的结果是 `T` 类型。这能工作是因为 `panic!` 并不产生一个值;它会终止程序。对于 `None` 的情况,`unwrap` 并不返回一个值,所以这些代码是有效。
最后一个有着 `!` 类型的表达式是 `loop`:
@@ -202,16 +191,11 @@ loop {
让我们深入研究一个贯穿本书都在使用的动态大小类型的细节:`str`。没错,不是 `&str`,而是 `str` 本身。`str` 是一个 DST;直到运行时我们都不知道字符串有多长。因为直到运行时都不能知道大其小,也就意味着不能创建 `str` 类型的变量,也不能获取 `str` 类型的参数。考虑一下这些代码,他们不能工作:
-```rust,ignore
+```rust,ignore,does_not_compile
let s1: str = "Hello there!";
let s2: str = "How's it going?";
```
-
-
-
Rust 需要知道应该为特定类型的值分配多少内存,同时所有同一类型的值必须使用相同数量的内存。如果允许编写这样的代码,也就意味着这两个 `str` 需要占用完全相同大小的空间,不过它们有着不同的长度。这也就是为什么不可能创建一个存放动态大小类型的变量的原因。
那么该怎么办呢?你已经知道了这种问题的答案:`s1` 和 `s2` 的类型是 `&str` 而不是 `str`。如果你回想第四章 “字符串 slice” 部分,slice 数据结储存了开始位置和 slice 的长度。
@@ -220,15 +204,6 @@ Rust 需要知道应该为特定类型的值分配多少内存,同时所有同
可以将 `str` 与所有类型的指针结合:比如 `Box` 或 `Rc`。事实上,之前我们已经见过了,不过是另一个动态大小类型:trait。每一个 trait 都是一个可以通过 trait 名称来引用的动态大小类型。在第十七章 “为使用不同类型的值而设计的 trait 对象” 部分,我们提到了为了将 trait 用于 trait 对象,必须将他们放入指针之后,比如 `&Trait` 或 `Box`(`Rc` 也可以)。trait 之所以是动态大小类型的是因为只有这样才能使用它。
-#### `Sized` trait
-
-
-
-
-
-
为了处理 DST,Rust 有一个特定的 trait 来决定一个类型的大小是否在编译时可知:这就是 `Sized` trait。这个 trait 自动为编译器在编译时就知道大小的类型实现。另外,Rust 隐式的为每一个泛型函数增加了 `Sized` bound。也就是说,对于如下泛型函数定义:
```rust,ignore
diff --git a/src/ch19-05-advanced-functions-and-closures.md b/src/ch19-05-advanced-functions-and-closures.md
index 74c4fdc..3929721 100644
--- a/src/ch19-05-advanced-functions-and-closures.md
+++ b/src/ch19-05-advanced-functions-and-closures.md
@@ -1,14 +1,14 @@
## 高级函数与闭包
-> [ch19-05-advanced-functions-and-closures.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch19-05-advanced-functions-and-closures.md)
+> [ch19-05-advanced-functions-and-closures.md](https://github.com/rust-lang/book/blob/master/src/ch19-05-advanced-functions-and-closures.md)
>
-> commit 509cb42ece610bdac8eaad26d57fb604dc078623
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
最后我们将探索一些有关函数和闭包的高级功能:函数指针以及返回值闭包。
### 函数指针
-我们讨论过了如何向函数传递闭包;也可以向函数传递常规函数!这在我们希望传递已经定义的函数而不是重新定义闭包作为参数是很有用。通过函数指针允许我们使用函数作为另一个函数的参数。函数的类型是 `fn` (使用小写的 “f” )以免与 `Fn` 闭包 trait 相混淆。`fn` 被称为**函数指针**(*function pointer*)。指定参数为函数指针的语法类似于闭包,如示例 19-34 所示:
+我们讨论过了如何向函数传递闭包;也可以向函数传递常规函数!这在我们希望传递已经定义的函数而不是重新定义闭包作为参数是很有用。通过函数指针允许我们使用函数作为另一个函数的参数。函数的类型是 `fn` (使用小写的 “f” )以免与 `Fn` 闭包 trait 相混淆。`fn` 被称为 **函数指针**(*function pointer*)。指定参数为函数指针的语法类似于闭包,如示例 19-35 所示:
文件名: src/main.rs
@@ -28,7 +28,7 @@ fn main() {
}
```
-示例 19-35: 使用 `fn` 类型接受函数指针作为参数
+示例 19-34: 使用 `fn` 类型接受函数指针作为参数
这会打印出 `The answer is: 12`。`do_twice` 中的 `f` 被指定为一个接受一个 `i32` 参数并返回 `i32` 的 `fn`。接着就可以在 `do_twice` 函数体中调用 `f`。在 `main` 中,可以将函数名 `add_one` 作为第一个参数传递给 `do_twice`。
@@ -36,7 +36,7 @@ fn main() {
函数指针实现了所有三个闭包 trait(`Fn`、`FnMut` 和 `FnOnce`),所以总是可以在调用期望闭包的函数时传递函数指针作为参数。倾向于编写使用泛型和闭包 trait 的函数,这样它就能接受函数或闭包作为参数。
-一个只期望接受 `fn` 而不接受闭包的情况的例子是与不存在闭包的外部代码交互时:C 语言的函数可以接受函数作为参数,但没有闭包。
+一个只期望接受 `fn` 而不接受闭包的情况的例子是与不存在闭包的外部代码交互时:C 语言的函数可以接受函数作为参数,但 C 语言没有闭包。
作为一个既可以使用内联定义的闭包又可以使用命名函数的例子,让我们看看一个 `map` 的应用。使用 `map` 函数将一个数字 vector 转换为一个字符串 vector,就可以使用闭包,比如这样:
@@ -60,6 +60,20 @@ let list_of_strings: Vec = list_of_numbers
注意这里必须使用 “高级 trait” 部分讲到的完全限定语法,因为存在多个叫做 `to_string` 的函数;这里使用了定义于 `ToString` trait 的 `to_string` 函数,标准库为所有实现了 `Display` 的类型实现了这个 trait。
+另一个实用的模式暴露了元组结构体和元组结构体枚举成员的实现细节。这些项使用 `()` 作为初始化语法,这看起来就像函数调用,同时它们确实被实现为返回由参数构造的实例的函数。它们也被称为实现了闭包 trait 的函数指针,并可以采用类似如下的方式调用:
+
+```rust
+enum Status {
+ Value(u32),
+ Stop,
+}
+
+let list_of_statuses: Vec =
+ (0u32..20)
+ .map(Status::Value)
+ .collect();
+```
+
一些人倾向于函数风格,一些人喜欢闭包。这两种形式最终都会产生同样的代码,所以请使用对你来说更明白的形式吧。
### 返回闭包
@@ -68,7 +82,7 @@ let list_of_strings: Vec = list_of_numbers
这段代码尝试直接返回闭包,它并不能编译:
-```rust,ignore
+```rust,ignore,does_not_compile
fn returns_closure() -> Fn(i32) -> i32 {
|x| x + 1
}
@@ -93,15 +107,11 @@ std::marker::Sized` is not satisfied
错误又一次指向了 `Sized` trait!Rust 并不知道需要多少空间来储存闭包。不过我们在上一部分见过这种情况的解决办法:可以使用 trait 对象:
```rust
-fn returns_closure() -> Box i32> {
+fn returns_closure() -> Box i32> {
Box::new(|x| x + 1)
}
```
这段代码正好可以编译。关于 trait 对象的更多内容,请回顾第十七章的 “为使用不同类型的值而设计的 trait 对象” 部分。
-## 总结
-
-好的!现在我们学习了 Rust 并不常用但在特定情况下你可能用得着的功能。我们介绍了很多复杂的主题,这样若你在错误信息提示或阅读他人代码时遇到他们,至少可以说之前已经见过这些概念和语法了。你可以使用本章作为一个解决方案的参考。
-
-接下来,我们将再开始一个项目,将本书所学的所有内容付与实践!
\ No newline at end of file
+接下来让我们学习宏!
diff --git a/src/ch19-06-macros.md b/src/ch19-06-macros.md
new file mode 100644
index 0000000..2ad91ce
--- /dev/null
+++ b/src/ch19-06-macros.md
@@ -0,0 +1,362 @@
+## 宏
+
+> [ch19-06-macros.md](https://github.com/rust-lang/book/blob/master/src/ch19-06-macros.md)
+>
+> commit 6babe749f8d97131b97c3cb9e7ca76e6115b90c1
+
+我们已经在本书中使用过像 `println!` 这样的宏了,不过还没完全探索什么是宏以及它是如何工作的。**宏**(*Macro*)指的是 Rust 中一系列的功能:
+
+* **声明**(*Declarative*)宏,使用 `macro_rules!`
+* **过程**(*Procedural*),其有三种类型:
+ * 自定义 `#[derive]` 宏
+ * 类属性(Attribute)宏
+ * 类函数宏
+
+我们会依次讨论每一种宏,不过首要的是,为什么已经有了函数还需要宏呢?
+
+### 宏和函数的区别
+
+从根本上来说,宏是一种为写其他代码而写代码的方式,即所谓的 **元编程**(*metaprogramming*)。在附录 C 中会探讨 `derive` 属性,其生成各种 trait 的实现。我们也在本书中使用过 `println!` 宏和 `vec!` 宏。所有的这些宏 **展开** 来以生成比你手写更多的代码。
+
+元编程对于减少大量编写和维护的代码是非常有用的,它也扮演了函数的角色。但宏有一些函数所没有的附加能力。
+
+一个函数标签必须声明函数参数个数和类型。相比之下,宏只接受一个可变参数:用一个参数调用 `println!("hello")` 或用两个参数调用 `println!("hello {}", name)` 。而且,宏可以在编译器翻译代码前展开,例如,宏可以在一个给定类型上实现 trait 。因为函数是在运行时被调用,同时 trait 需要在运行时实现,所以函数无法像宏这样。
+
+实现一个宏而不是函数的消极面是宏定义要比函数定义更复杂,因为你正在编写生成 Rust 代码的 Rust 代码。由于这样的间接性,宏定义通常要比函数定义更难阅读、理解以及维护。
+
+宏和函数的最后一个重要的区别是:在调用宏 **之前** 必须定义并将其引入作用域,而函数则可以在任何地方定义和调用。
+
+### 使用 `macro_rules!` 的声明宏用于通用元编程
+
+Rust 最常用的宏形式是 **声明宏**(*declarative macros*)。它们有时也被称为 “macros by example”、“`macro_rules!` 宏” 或者就是 “macros”。其核心概念是,声明宏允许我们编写一些类似 Rust `match` 表达式的代码。正如在第六章讨论的那样,`match` 表达式是控制结构,其接收一个表达式,与表达式的结果进行模式匹配,然后根据模式匹配执行相关代码。宏也将一个值和包含相关代码的模式进行比较;此种情况下,该值是传递给宏的 Rust 源代码字面值,模式用于和传递给宏的源代码进行比较,同时每个模式的相关代码则用于替换传递给宏的代码。所有这一切都发生于编译时。
+
+可以使用 `macro_rules!` 来定义宏。让我们通过查看 `vec!` 宏定义来探索如何使用 `macro_rules!` 结构。第八章讲述了如何使用 `vec!` 宏来生成一个给定值的 vector。例如,下面的宏用三个整数创建一个 vector `:
+
+```rust
+let v: Vec = vec![1, 2, 3];
+```
+
+也可以使用 `vec!` 宏来构造两个整数的 vector 或五个字符串 slice 的 vector 。但却无法使用函数做相同的事情,因为我们无法预先知道参数值的数量和类型。
+
+在示例 19-36 中展示了一个 `vec!` 稍微简化的定义。
+
+文件名: src/lib.rs
+
+```rust
+#[macro_export]
+macro_rules! vec {
+ ( $( $x:expr ),* ) => {
+ {
+ let mut temp_vec = Vec::new();
+ $(
+ temp_vec.push($x);
+ )*
+ temp_vec
+ }
+ };
+}
+```
+
+示例 19-36: 一个 `vec!` 宏定义的简化版本
+
+> 注意:标准库中实际定义的 `vec!` 包括预分配适当量的内存的代码。这部分为代码优化,为了让示例简化,此处并没有包含在内。
+
+
+无论何时导入定义了宏的包,`#[macro_export]` 注解说明宏应该是可用的。 如果没有该注解,这个宏不能被引入作用域。
+
+接着使用 `macro_rules!` 和宏名称开始宏定义,且所定义的宏并 *不带* 感叹号。名字后跟大括号表示宏定义体,在该例中宏名称是 `vec` 。
+
+`vec!` 宏的结构和 `match` 表达式的结构类似。此处有一个单边模式 `( $( $x:expr ),* )` ,后跟 `=>` 以及和模式相关的代码块。如果模式匹配,该相关代码块将被执行。假设这只是这个宏中的模式,且只有一个有效匹配,其他任何匹配都是错误的。更复杂的宏会有多个单边模式。
+
+宏定义中有效模式语法和在第十八章提及的模式语法是不同的,因为宏模式所匹配的是 Rust 代码结构而不是值。回过头来检查下示例 D-1 中模式片段什么意思。对于全部的宏模式语法,请查阅[参考]。
+
+[参考]: https://doc.rust-lang.org/reference/macros.html
+
+首先,一对括号包含了全部模式。接下来是后跟一对括号的美元符号( `$` ),其通过替代代码捕获了符合括号内模式的值。`$()` 内则是 `$x:expr` ,其匹配 Rust 的任意表达式或给定 `$x` 名字的表达式。
+
+`$()` 之后的逗号说明一个逗号分隔符可以有选择的出现代码之后,这段代码与在 `$()` 中所捕获的代码相匹配。紧随逗号之后的 `*` 说明该模式匹配零个或多个 `*` 之前的任何模式。
+
+当以 `vec![1, 2, 3];` 调用宏时,`$x` 模式与三个表达式 `1`、`2` 和 `3` 进行了三次匹配。
+
+现在让我们来看看这个出现在与此单边模式相关的代码块中的模式:在 `$()*` 部分中所生成的 `temp_vec.push()` 为在匹配到模式中的 `$()` 每一部分而生成。`$x` 由每个与之相匹配的表达式所替换。当以 `vec![1, 2, 3];` 调用该宏时,替换该宏调用所生成的代码会是下面这样:
+
+```rust,ignore
+let mut temp_vec = Vec::new();
+temp_vec.push(1);
+temp_vec.push(2);
+temp_vec.push(3);
+temp_vec
+```
+
+我们已经定义了一个宏,其可以接收任意数量和类型的参数,同时可以生成能够创建包含指定元素的 vector 的代码。
+
+`macro_rules!` 中有一些奇怪的地方。在将来,会有第二种采用 `macro` 关键字的声明宏,其工作方式类似但修复了这些极端情况。在此之后,`macro_rules!` 实际上就过时(deprecated)了。在此基础之上,同时鉴于大多数 Rust 程序员 **使用** 宏而非 **编写** 宏的事实,此处不再深入探讨 `macro_rules!`。请查阅在线文档或其他资源,如 [“The Little Book of Rust Macros”][tlborm] 来更多地了解如何写宏。
+
+[tlborm]: https://danielkeep.github.io/tlborm/book/index.html
+
+### 用于从属性生成代码的过程宏
+
+第二种形式的宏被称为 **过程宏**(*procedural macros*),因为它们更像函数(一种过程类型)。过程宏接收 Rust 代码作为输入,在这些代码上进行操作,然后产生另一些代码作为输出,而非像声明式宏那样匹配对应模式然后以另一部分代码替换当前代码。
+
+有三种类型的过程宏,不过它们的工作方式都类似。其一,其定义必须位于一种特殊类型的属于它们自己的 crate 中。这么做出于复杂的技术原因,将来我们希望能够消除这些限制。
+
+其二,使用这些宏需采用类似示例 19-37 所示的代码形式,其中 `some_attribute` 是一个使用特定宏的占位符。
+
+文件名: src/lib.rs
+
+```rust,ignore
+use proc_macro;
+
+#[some_attribute]
+pub fn some_name(input: TokenStream) -> TokenStream {
+}
+```
+
+示例 19-37: 一个使用过程宏的例子
+
+过程宏包含一个函数,这也是其得名的原因:“过程” 是 “函数” 的同义词。那么为何不叫 “函数宏” 呢?好吧,有一个过程宏是 “类函数” 的,叫成函数会产生混乱。无论如何,定义过程宏的函数接受一个 `TokenStream` 作为输入并产生一个 `TokenStream` 作为输出。这也就是宏的核心:宏所处理的源代码组成了输入 `TokenStream`,同时宏生成的代码是输出 `TokenStream`。最后,函数上有一个属性;这个属性表明过程宏的类型。在同一 crate 中可以有多种的过程宏。
+
+考虑到这些宏是如此类似,我们会从自定义派生宏开始。接着会解释与其他形式宏的微小区别。
+
+### 如何编写自定义 `derive` 宏
+
+让我们创建一个 `hello_macro` crate,其包含名为 `HelloMacro` 的 trait 和关联函数 `hello_macro`。不同于让 crate 的用户为其每一个类型实现 `HelloMacro` trait,我们将会提供一个过程式宏以便用户可以使用 `#[derive(HelloMacro)]` 注解他们的类型来得到 `hello_macro` 函数的默认实现。该默认实现会打印 `Hello, Macro! My name is TypeName!`,其中 `TypeName` 为定义了 trait 的类型名。换言之,我们会创建一个 crate,使程序员能够写类似示例 19-38 中的代码。
+
+文件名: src/main.rs
+
+```rust,ignore
+use hello_macro::HelloMacro;
+use hello_macro_derive::HelloMacro;
+
+#[derive(HelloMacro)]
+struct Pancakes;
+
+fn main() {
+ Pancakes::hello_macro();
+}
+```
+
+示例 19-38: crate 用户所写的能够使用过程式宏的代码
+
+运行该代码将会打印 `Hello, Macro! My name is Pancakes!` 第一步是像下面这样新建一个库 crate:
+
+```text
+$ cargo new hello_macro --lib
+```
+
+接下来,会定义 `HelloMacro` trait 以及其关联函数:
+
+文件名: src/lib.rs
+
+```rust
+pub trait HelloMacro {
+ fn hello_macro();
+}
+```
+
+现在有了一个包含函数的 trait 。此时,crate 用户可以实现该 trait 以达到其期望的功能,像这样:
+
+```rust,ignore
+use hello_macro::HelloMacro;
+
+struct Pancakes;
+
+impl HelloMacro for Pancakes {
+ fn hello_macro() {
+ println!("Hello, Macro! My name is Pancakes!");
+ }
+}
+
+fn main() {
+ Pancakes::hello_macro();
+}
+```
+
+然而,他们需要为每一个他们想使用 `hello_macro` 的类型编写实现的代码块。我们希望为其节约这些工作。
+
+另外,我们也无法为 `hello_macro` 函数提供一个能够打印实现了该 trait 的类型的名字的默认实现:Rust 没有反射的能力,因此其无法在运行时获取类型名。我们需要一个在运行时生成代码的宏。
+
+下一步是定义过程式宏。在编写本部分时,过程式宏必须在其自己的 crate 内。该限制最终可能被取消。构造 crate 和其中宏的惯例如下:对于一个 `foo` 的包来说,一个自定义的派生过程宏的包被称为 `foo_derive` 。在 `hello_macro` 项目中新建名为 `hello_macro_derive` 的包。
+
+```text
+$ cargo new hello_macro_derive --lib
+```
+
+由于两个 crate 紧密相关,因此在 `hello_macro` 包的目录下创建过程式宏的 crate。如果改变在 `hello_macro` 中定义的 trait ,同时也必须改变在 `hello_macro_derive` 中实现的过程式宏。这两个包需要分别发布,编程人员如果使用这些包,则需要同时添加这两个依赖并将其引入作用域。我们也可以只用 `hello_macro` 包而将 `hello_macro_derive` 作为一个依赖,并重新导出过程式宏的代码。但我们组织项目的方式使编程人员使用 `hello_macro` 成为可能,即使他们无需 `derive` 的功能。
+
+需要将 `hello_macro_derive` 声明为一个过程宏的 crate。同时也需要 `syn` 和 `quote` crate 中的功能,正如注释中所说,需要将其加到依赖中。为 `hello_macro_derive` 将下面的代码加入到 *Cargo.toml* 文件中。
+
+文件名: hello_macro_derive/Cargo.toml
+
+```toml
+[lib]
+proc-macro = true
+
+[dependencies]
+syn = "0.14.4"
+quote = "0.6.3"
+```
+
+为定义一个过程式宏,请将示例 19-39 中的代码放在 `hello_macro_derive` crate 的 *src/lib.rs* 文件里面。注意这段代码在我们添加 `impl_hello_macro` 函数的定义之前是无法编译的。
+
+文件名: hello_macro_derive/src/lib.rs
+
+
+
+> 在 Rust 1.31.0 时,`extern crate` 仍是必须的,请查看
+> https://github.com/rust-lang/rust/issues/54418
+> https://github.com/rust-lang/rust/pull/54658
+> https://github.com/rust-lang/rust/issues/55599
+
+```rust,ignore
+extern crate proc_macro;
+
+use crate::proc_macro::TokenStream;
+use quote::quote;
+use syn;
+
+#[proc_macro_derive(HelloMacro)]
+pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
+ // 构建 Rust 代码所代表的语法树
+ // 以便可以进行操作
+ let ast = syn::parse(input).unwrap();
+
+ // 构建 trait 实现
+ impl_hello_macro(&ast)
+}
+```
+
+示例 19-39: 大多数过程式宏处理 Rust 代码时所需的代码
+
+注意在 19-39 中分离函数的方式,这将和你几乎所见到或创建的每一个过程宏都一样,因为这让编写一个过程式宏更加方便。在 `impl_hello_macro` 被调用的地方所选择做的什么依赖于该过程式宏的目的而有所不同。
+
+现在,我们已经引入了 三个新的 crate:`proc_macro` 、 [`syn`] 和 [`quote`] 。Rust 自带 `proc_macro` crate,因此无需将其加到 *Cargo.toml* 文件的依赖中。`proc_macro` crate 是编译器用来读取和操作我们 Rust 代码的 API。`syn` crate 将字符串中的 Rust 代码解析成为一个可以操作的数据结构。`quote` 则将 `syn` 解析的数据结构反过来传入到 Rust 代码中。这些 crate 让解析任何我们所要处理的 Rust 代码变得更简单:为 Rust 编写整个的解析器并不是一件简单的工作。
+
+[`syn`]: https://crates.io/crates/syn
+[`quote`]: https://crates.io/crates/quote
+
+当用户在一个类型上指定 `#[derive(HelloMacro)]` 时,`hello_macro_derive` 函数将会被调用。原因在于我们已经使用 `proc_macro_derive` 及其指定名称对 `hello_macro_derive` 函数进行了注解:`HelloMacro` ,其匹配到 trait 名,这是大多数过程宏遵循的习惯。
+
+该函数首先将来自 `TokenStream` 的 `input` 转换为一个我们可以解释和操作的数据结构。这正是 `syn` 派上用场的地方。`syn` 中的 `parse_derive_input` 函数获取一个 `TokenStream` 并返回一个表示解析出 Rust 代码的 `DeriveInput` 结构体。示例 19-40 展示了从字符串 `struct Pancakes;` 中解析出来的 `DeriveInput` 结构体的相关部分:
+
+```rust,ignore
+DeriveInput {
+ // --snip--
+
+ ident: Ident {
+ ident: "Pancakes",
+ span: #0 bytes(95..103)
+ },
+ data: Struct(
+ DataStruct {
+ struct_token: Struct,
+ fields: Unit,
+ semi_token: Some(
+ Semi
+ )
+ }
+ )
+}
+```
+
+示例 19-40: 解析示例 19-38 中带有宏属性的代码时得到的 `DeriveInput` 实例
+
+该结构体的字段展示了我们解析的 Rust 代码是一个类单元结构体,其 `ident`( identifier,表示名字)为 `Pancakes`。该结构体里面有更多字段描述了所有类型的 Rust 代码,查阅 [`syn` 中 `DeriveInput` 的文档][syn-docs] 以获取更多信息。
+
+[syn-docs]: https://docs.rs/syn/0.14.4/syn/struct.DeriveInput.html
+
+此时,尚未定义 `impl_hello_macro` 函数,其用于构建所要包含在内的 Rust 新代码。但在此之前,注意其输出也是 `TokenStream`。所返回的 `TokenStream` 会被加到我们的 crate 用户所写的代码中,因此,当用户编译他们的 crate 时,他们会获取到我们所提供的额外功能。
+
+你可能也注意到了,当调用 `parse_derive_input` 或 `parse` 失败时。在错误时 panic 对过程宏来说是必须的,因为 `proc_macro_derive` 函数必须返回 `TokenStream` 而不是 `Result`,以此来符合过程宏的 API。这里选择用 `unwrap` 来简化了这个例子;在生产代码中,则应该通过 `panic!` 或 `expect` 来提供关于发生何种错误的更加明确的错误信息。
+
+现在我们有了将注解的 Rust 代码从 `TokenStream` 转换为 `DeriveInput` 实例的代码,让我们来创建在注解类型上实现 `HelloMacro` trait 的代码,如示例 19-41 所示。
+
+文件名: hello_macro_derive/src/lib.rs
+
+```rust,ignore
+fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream {
+ let name = &ast.ident;
+ let gen = quote! {
+ impl HelloMacro for #name {
+ fn hello_macro() {
+ println!("Hello, Macro! My name is {}", stringify!(#name));
+ }
+ }
+ };
+ gen.into()
+}
+```
+
+示例 19-41: 使用解析过的 Rust 代码实现 `HelloMacro` trait
+
+我们得到一个包含以 `ast.ident` 作为注解类型名字(标识符)的 `Ident` 结构体实例。示例 19-40 中的结构体表明当 `impl_hello_macro` 函数运行于示例 19-38 中的代码上时 `ident` 字段的值是 `"Pancakes"`。因此,示例 19-41 中 `name` 变量会包含一个 `Ident` 结构体的实例,当打印时,会是字符串 `"Pancakes"`,也就是示例 19-38 中结构体的名称。
+
+`quote!` 让我们可以编写希望返回的 Rust diamagnetic。`quote!` 宏执行的直接结果并不是编译器所期望的并需要转换为 `TokenStream`。为此需要调用 `into` 方法,它会消费这个中间表示(intermediate representation,IR)并返回所需的 `TokenStream` 类型值。
+
+这个宏也提供了一些非常酷的模板机制;我们可以写 `#name` ,然后 `quote!` 会以 名为 `name` 的变量值来替换它。你甚至可以做些与这个正则宏任务类似的重复事情。查阅 [`quote` crate 的文档][quote-docs] 来获取详尽的介绍。
+
+[quote-docs]: https://docs.rs/quote
+
+我们期望我们的过程式宏能够为通过 `#name` 获取到的用户注解类型生成 `HelloMacro` trait 的实现。该 trait 的实现有一个函数 `hello_macro` ,其函数体包括了我们期望提供的功能:打印 `Hello, Macro! My name is` 和注解的类型名。
+
+此处所使用的 `stringify!` 为 Rust 内置宏。其接收一个 Rust 表达式,如 `1 + 2` , 然后在编译时将表达式转换为一个字符串常量,如 `"1 + 2"` 。这与 `format!` 或 `println!` 是不同的,它计算表达式并将结果转换为 `String` 。有一种可能的情况是,所输入的 `#name` 可能是一个需要打印的表达式,因此我们用 `stringify!` 。 `stringify!` 编译时也保留了一份将 `#name` 转换为字符串之后的内存分配。
+
+此时,`cargo build` 应该都能成功编译 `hello_macro` 和 `hello_macro_derive` 。我们将这些 crate 连接到示例 19-38 的代码中来看看过程宏的行为!在 *projects* 目录下用 `cargo new pancakes` 命令新建一个二进制项目。需要将 `hello_macro` 和 `hello_macro_derive` 作为依赖加到 `pancakes` 包的 *Cargo.toml* 文件中去。如果你正将 `hello_macro` 和 `hello_macro_derive` 的版本发布到 *https://crates.io/* 上,其应为正规依赖;如果不是,则可以像下面这样将其指定为 `path` 依赖:
+
+```toml
+[dependencies]
+hello_macro = { path = "../hello_macro" }
+hello_macro_derive = { path = "../hello_macro/hello_macro_derive" }
+```
+
+把示例 19-38 中的代码放在 *src/main.rs* ,然后执行 `cargo run`:其应该打印 `Hello, Macro! My name is Pancakes!`。其包含了该过程宏中 `HelloMacro` trait 的实现,而无需 `pancakes` crate 实现它;`#[derive(HelloMacro)]` 增加了该 trait 实现。
+
+接下来,让我们探索一下其他类型的过程宏与自定义派生宏有何区别。
+
+### 类属性宏
+
+类属性宏与自定义派生宏相似,不同于为 `derive` 属性生成代码,它们允许你创建新的属性。它们也更为灵活;`derive` 只能用于结构体和枚举;属性还可以用于其它的项,比如函数。作为一个使用类属性宏的例子,可以创建一个名为 `route` 的属性用于注解 web 应用程序框架(web application framework)的函数:
+
+```rust,ignore
+#[route(GET, "/")]
+fn index() {
+```
+
+`#[route]` 属性将由框架本身定义为一个过程宏。其宏定义的函数签名看起来像这样:
+
+```rust,ignore
+#[proc_macro_attribute]
+pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {
+```
+
+这里有两个 `TokenStream` 类型的参数;第一个用于属性内容本身,也就是 `GET, "/"` 部分。第二个是属性所标记的项,在本例中,是 `fn index() {}` 和剩下的函数体。
+
+除此之外,类属性宏与自定义派生宏工作方式一致:创建 `proc-macro` crate 类型的 crate 并实现希望生成代码的函数!
+
+### 类函数宏
+
+最后,类函数宏定义看起来像函数调用的宏。例如,`sql!` 宏可能像这样被调用:
+
+```rust,ignore
+let sql = sql!(SELECT * FROM posts WHERE id=1);
+```
+
+这个宏会解析其中的 SQL 语句并检查其是否是句法正确的。该宏应该被定义为如此:
+
+```rust,ignore
+#[proc_macro]
+pub fn sql(input: TokenStream) -> TokenStream {
+```
+
+这类似于自定义派生宏的签名:获取括号中的 token,并返回希望生成的代码。
+
+## 总结
+
+好的!现在我们学习了 Rust 并不常用但在特定情况下你可能用得着的功能。我们介绍了很多复杂的主题,这样若你在错误信息提示或阅读他人代码时遇到他们,至少可以说之前已经见过这些概念和语法了。你可以使用本章作为一个解决方案的参考。
+
+接下来,我们将再开始一个项目,将本书所学的所有内容付与实践!
\ No newline at end of file
diff --git a/src/ch20-00-final-project-a-web-server.md b/src/ch20-00-final-project-a-web-server.md
index de5b28d..873ae3d 100644
--- a/src/ch20-00-final-project-a-web-server.md
+++ b/src/ch20-00-final-project-a-web-server.md
@@ -1,16 +1,16 @@
# 最后的项目: 构建多线程 web server
-> [ch20-00-final-project-a-web-server.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch20-00-final-project-a-web-server.md)
+> [ch20-00-final-project-a-web-server.md](https://github.com/rust-lang/book/blob/master/src/ch20-00-final-project-a-web-server.md)
>
-> commit e2a38b44f3a7f796fa8000e558dc8dd2ddf340a3
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
-这是一次漫长的旅途,不过我们做到了!这一章便是本书的结束。离别是如此甜蜜的悲伤。不过在我们结束之前,再来一起构建另一个项目,来展示最后几章所学,同时复习更早的章节。
+这是一次漫长的旅途,不过我们到达了本书的结束。在本章中,我们将一同构建另一个项目,来展示最后几章所学,同时复习更早的章节。
-作为最后的项目,我们将要实现一个只返回 “hello” 的 web server;它在浏览器中看起来就如图例 20-1 所示:
+作为最后的项目,我们将要实现一个返回 “hello” 的 web server,它在浏览器中看起来就如图例 20-1 所示:
![hello from rust](img/trpl20-01.png)
-图例 20-1: 我们最好将一起分享的项目
+图例 20-1: 我们最后将一起分享的项目
如下是我们将怎样构建此 web server 的计划:
@@ -20,6 +20,6 @@
4. 创建一个合适的 HTTP 响应
5. 通过线程池改善 server 的吞吐量
-不过在开始之前,需要提到一点:这里使用的方法并不是使用 Rust 构建 web server 最好的方法。*https://crates.io* 上有很多可用于生产环境的 crate,它们提供了比我们所要编写的更为完整的 web server 和线程池实现。
+不过在开始之前,需要提到一点细节:这里使用的方法并不是使用 Rust 构建 web server 最好的方法。*https://crates.io* 上有很多可用于生产环境的 crate,它们提供了比我们所要编写的更为完整的 web server 和线程池实现。
然而,本章的目的在于学习,而不是走捷径。因为 Rust 是一个系统编程语言,我们能够选择处理什么层次的抽象,并能够选择比其他语言可能或可用的层次更低的层次。因此我们将自己编写一个基础的 HTTP server 和线程池,以便学习将来可能用到的 crate 背后的通用理念和技术。
diff --git a/src/ch20-01-single-threaded.md b/src/ch20-01-single-threaded.md
index 4ec672c..a3a4355 100644
--- a/src/ch20-01-single-threaded.md
+++ b/src/ch20-01-single-threaded.md
@@ -1,10 +1,10 @@
## 构建单线程 web server
-> [ch20-01-single-threaded.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch20-01-single-threaded.md)
+> [ch20-01-single-threaded.md](https://github.com/rust-lang/book/blob/master/src/ch20-01-single-threaded.md)
>
-> commit 90e6737d534cb66102674d183d2ef1966b190c2c
+> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
-首先让我们创建一个可运行的单线程 web server,不过在开始之前,我们将快速了解一下构建 web server 所涉及到的协议。这些协议的细节超出了本书的范畴,不过一个简单的概括会提供你所需的信息。
+首先让我们创建一个可运行的单线程 web server,不过在开始之前,我们将快速了解一下构建 web server 所涉及到的协议。这些协议的细节超出了本书的范畴,不过一个简单的概括会提供我们所需的信息。
web server 中涉及到的两个主要协议是 **超文本传输协议**(*Hypertext Transfer Protocol*,*HTTP*)和 **传输控制协议**(*Transmission Control Protocol*,*TCP*)。这两者都是 **请求-响应**(*request-response*)协议,也就是说,有 **客户端**(*client*)来初始化请求,并有 **服务端**(*server*)监听请求并向客户端提供响应。请求与响应的内容由协议本身定义。
@@ -15,7 +15,7 @@ TCP 是一个底层协议,它描述了信息如何从一个 server 到另一
所以我们的 web server 所需做的第一件事便是能够监听 TCP 连接。标准库提供了 `std::net` 模块处理这些功能。让我们一如既往新建一个项目:
```text
-$ cargo new hello --bin
+$ cargo new hello
Created binary (application) `hello` project
$ cd hello
```
@@ -40,28 +40,16 @@ fn main() {
示例 20-1: 监听传入的流并在接收到流时打印信息
-`TcpListener` 用于监听 TCP 连接。我们选择监听地址 `127.0.0.1:7878`。将这个地址拆开,冒号之前的部分是一个代表本机的 IP 地址(这个地址在每台计算机上都相同,并不特指作者的计算机),而 `7878` 是端口。选择这个端口出于两个原因:通常 HTTP 接受这个端口而且 7878 在电话上打出来就是 "rust"(译者注:九宫格键盘上的英文)。注意连接 80 端口需要管理员权限;非管理员用户只能监听大于 1024 的端口。
+`TcpListener` 用于监听 TCP 连接。我们选择监听地址 `127.0.0.1:7878`。将这个地址拆开,冒号之前的部分是一个代表本机的 IP 地址(这个地址在每台计算机上都相同,并不特指作者的计算机),而 `7878` 是端口。选择这个端口出于两个原因:通常 HTTP 接受这个端口而且 7878 在电话上打出来就是 "rust"(译者注:九宫格键盘上的英文)。
在这个场景中 `bind` 函数类似于 `new` 函数,在这里它返回一个新的 `TcpListener` 实例。这个函数叫做 `bind` 是因为,在网络领域,连接到监听端口被称为 “绑定到一个端口”(“binding to a port”)
-`bind` 函数返回 `Result`,这表明绑定可能会失败,例如,如果不是管理员尝试连接 80 端口,或是如果运行两个此程序的实例这样会有两个程序监听相同的端口,绑定会失败。因为我们是出于学习目的来编写一个基础的 server,将不用关心处理这类错误,使用 `unwrap` 在出现这些情况时直接停止程序。
+`bind` 函数返回 `Result`,这表明绑定可能会失败,例如,连接 80 端口需要管理员权限(非管理员用户只能监听大于 1024 的端口),所以如果不是管理员尝试连接 80 端口,则会绑定失败。另一个例子是如果运行两个此程序的实例这样会有两个程序监听相同的端口,绑定会失败。因为我们是出于学习目的来编写一个基础的 server,将不用关心处理这类错误,使用 `unwrap` 在出现这些情况时直接停止程序。
`TcpListener` 的 `incoming` 方法返回一个迭代器,它提供了一系列的流(更准确的说是 `TcpStream` 类型的流)。**流**(*stream*)代表一个客户端和服务端之间打开的连接。**连接**(*connection*)代表客户端连接服务端、服务端生成响应以及服务端关闭连接的全部请求 / 响应过程。为此,`TcpStream` 允许我们读取它来查看客户端发送了什么,并可以编写响应。总体来说,这个 `for` 循环会依次处理每个连接并产生一系列的流供我们处理。
-
-
-
目前为止,处理流的过程包含 `unwrap` 调用,如果出现任何错误会终止程序,如果没有任何错误,则打印出信息。下一个示例我们将为成功的情况增加更多功能。当客户端连接到服务端时 `incoming` 方法返回错误是可能的,因为我们实际上没有遍历连接,而是遍历 **连接尝试**(*connection attempts*)。连接可能会因为很多原因不能成功,大部分是操作系统相关的。例如,很多系统限制同时打开的连接数;新连接尝试产生错误,直到一些打开的连接关闭为止。
-
-
让我们试试这段代码!首先在终端执行 `cargo run`,接着在浏览器中加载 `127.0.0.1:7878`。浏览器会显示出看起来类似于“连接重置”(“Connection reset”)的错误信息,因为 server 目前并没响应任何数据。但是如果我们观察终端,会发现当浏览器连接 server 时会打印出一系列的信息!
```text
@@ -71,11 +59,11 @@ Connection established!
Connection established!
```
-有时会看到对于一次浏览器请求会打印出多条信息;这可能是因为浏览器在请求页面的同时还请求了其他资源,比如出现在浏览器 tab 标签中的 `favicon.ico`。
+有时会看到对于一次浏览器请求会打印出多条信息;这可能是因为浏览器在请求页面的同时还请求了其他资源,比如出现在浏览器 tab 标签中的 *favicon.ico*。
这也可能是因为浏览器尝试多次连接 server,因为 server 没有响应任何数据。当 `stream` 在循环的结尾离开作用域并被丢弃,其连接将被关闭,作为 `drop` 实现的一部分。浏览器有时通过重连来处理关闭的连接,因为这些问题可能是暂时的。现在重要的是我们成功的处理了 TCP 连接!
-记得当运行完特定版本的代码后使用 ctrl-C 来停止程序,并在做出最新的代码修改之后执行 `cargo run` 重启服务。
+记得当运行完特定版本的代码后使用 ctrl-C 来停止程序。并在做出最新的代码修改之后执行 `cargo run` 重启服务。
### 读取请求
@@ -85,8 +73,8 @@ Connection established!
```rust,no_run
use std::io::prelude::*;
-use std::net::TcpListener;
use std::net::TcpStream;
+use std::net::TcpListener;
fn main() {
let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
@@ -111,18 +99,11 @@ fn handle_connection(mut stream: TcpStream) {
这里将 `std::io::prelude` 引入作用域来获取读写流所需的特定 trait。在 `main` 函数的 `for` 循环中,相比获取到连接时打印信息,现在调用新的 `handle_connection` 函数并向其传递 `stream`。
-在 `handle_connection` 中,`stream` 参数是可变的。
-
-我们将从流中读取数据,所以它需要是可修改的。这是因为 `TcpStream` 实例在内部记录了所返回的数据。它可能读取了多于我们请求的数据并保存它们以备下一次请求数据。因此它需要是 `mut` 的因为其内部状态可能会改变;通常我们认为 “读取” 不需要可变性,不过在这个例子中则需要 `mut` 关键字。
-
-
-
+在 `handle_connection` 中,`stream` 参数是可变的。这是因为 `TcpStream` 实例在内部记录了所返回的数据。它可能读取了多于我们请求的数据并保存它们以备下一次请求数据。因此它需要是 `mut` 的因为其内部状态可能会改变;通常我们认为 “读取” 不需要可变性,不过在这个例子中则需要 `mut` 关键字。
接下来,需要实际读取流。这里分两步进行:首先,在栈上声明一个 `buffer` 来存放读取到的数据。这里创建了一个 512 字节的缓冲区,它足以存放基本请求的数据并满足本章的目的需要。如果希望处理任意大小的请求,缓冲区管理将更为复杂,不过现在一切从简。接着将缓冲区传递给 `stream.read` ,它会从 `TcpStream` 中读取字节并放入缓冲区中。
-接下来将缓冲区中的字节转换为字符串并打印出来。`String::from_utf8_lossy` 函数获取一个 `&[u8]` 并产生一个 `String`。函数名的 “lossy” 部分来源于当其遇到无效的 UTF-8 序列时的行为:它使用 �,`U+FFFD REPLACEMENT CHARACTER`,来代替无效序列。你可能会在缓冲区的剩余部分看到这些替代字符,因为他们没有被请求数据填满。
+接下来将缓冲区中的字节转换为字符串并打印出来。`String::from_utf8_lossy` 函数获取一个 `&[u8]` 并产生一个 `String`。函数名的 “lossy” 部分来源于当其遇到无效的 UTF-8 序列时的行为:它使用 `�`,`U+FFFD REPLACEMENT CHARACTER`,来代替无效序列。你可能会在缓冲区的剩余部分看到这些替代字符,因为他们没有被请求数据填满。
让我们试一试!启动程序并再次在浏览器中发起请求。注意浏览器中仍然会出现错误页面,不过终端中程序的输出现在看起来像这样:
@@ -143,7 +124,7 @@ Upgrade-Insecure-Requests: 1
������������������������������������
```
-根据使用的浏览器不同可能会出现稍微不同的数据。现在我们打印出了请求数据,可以通过观察 `Request: GET` 之后的路径来解释为何会从浏览器得到多个连接。如果重复的连接都是请求 `/`,就知道了浏览器尝试重复获取 `/` 因为它没有从程序得到响应。
+根据使用的浏览器不同可能会出现稍微不同的数据。现在我们打印出了请求数据,可以通过观察 `Request: GET` 之后的路径来解释为何会从浏览器得到多个连接。如果重复的连接都是请求 */*,就知道了浏览器尝试重复获取 */* 因为它没有从程序得到响应。
拆开请求数据来理解浏览器向程序请求了什么。
@@ -159,19 +140,11 @@ message-body
第一行叫做 **请求行**(*request line*),它存放了客户端请求了什么的信息。请求行的第一部分是所使用的 *method*,比如 `GET` 或 `POST`,这描述了客户端如何进行请求。这里客户端使用了 `GET` 请求。
-
-
-
-`Request` 行接下来的部分是 `/`,它代表客户端请求的 **统一资源标识符**(*Uniform Resource Identifier*,*URI*) —— URI 大体上类似,但也不完全类似于 URL(**统一资源定位符**,*Uniform Resource Locators*)。URI 和 URL 之间的区别对于本章的目的来说并不重要,不过 HTTP 规范使用术语 URI,所以这里可以简单的将 URL 理解为 URI。
+请求行接下来的部分是 */*,它代表客户端请求的 **统一资源标识符**(*Uniform Resource Identifier*,*URI*) —— URI 大体上类似,但也不完全类似于 URL(**统一资源定位符**,*Uniform Resource Locators*)。URI 和 URL 之间的区别对于本章的目的来说并不重要,不过 HTTP 规范使用术语 URI,所以这里可以简单的将 URL 理解为 URI。
-最后,是客户端使用的 HTTP 版本,接着请求行以一个 CRLF 序列结尾。CRLF 序列也可以写作 `\r\n`:`\r` 是 **回车**(*carriage return*)而 `\n` 是 **换行**(*line feed*)(这些术语来自打字机时代!)。注意当 CRLF 被打印时,会看到开始了一个新行而不是 `\r\n`。
+最后,是客户端使用的 HTTP 版本,接着请求行以一个 **CRLF 序列**(CRLF 是**回车**,*carriage return* 和 **换行**,*line feed* 的缩写,这些术语来自打字机时代!)结尾。结尾。CRLF 序列也可以写作 `\r\n`:`\r` 是回车而 `\n` 是换行。CRLF 序列将请求行与其余的请求数据分开。注意当 CRLF 被打印时,会看到开始了一个新行而不是 `\r\n`。
-
-
-
-观察目前运行程序所接收到的数据的请求行,可以看到 `GET` 是 method,`/` 是请求 URI,而 `HTTP/1.1` 是版本。
+观察目前运行程序所接收到的数据的请求行,可以看到 `GET` 是 method,*/* 是请求 URI,而 `HTTP/1.1` 是版本。
从 `Host:` 开始的其余的行是 headers;`GET` 请求没有 body。
@@ -191,15 +164,13 @@ message-body
第一行叫做 **状态行**(*status line*),它包含响应的 HTTP 版本、一个数字状态码用以总结请求的结果和一个描述之前状态码的文本原因短语。CRLF 序列之后是任意 header,另一个 CRLF 序列,和响应的 body。
-这里是一个使用 HTTP 1.1 版本的响应例子,其状态码为 `200`,原因短语为 `OK`,没有 header,也没有 body:
+这里是一个使用 HTTP 1.1 版本的响应例子,其状态码为 200,原因短语为 OK,没有 header,也没有 body:
```text
HTTP/1.1 200 OK\r\n\r\n
```
-状态码 200 是一个标准的成功响应。这些文本是一个微型的成功 HTTP 响应。让我们将这些文本写入流作为成功请求的响应!
-
-在 `handle_connection` 函数中,我们需要去掉打印请求数据的 `println!`,并替换为示例 20-3 中的代码:
+状态码 200 是一个标准的成功响应。这些文本是一个微型的成功 HTTP 响应。让我们将这些文本写入流作为成功请求的响应!在 `handle_connection` 函数中,我们需要去掉打印请求数据的 `println!`,并替换为示例 20-3 中的代码:
文件名: src/main.rs
@@ -220,26 +191,11 @@ fn handle_connection(mut stream: TcpStream) {
示例 20-3: 将一个微型成功 HTTP 响应写入流
-
-
新代码中的第一行定义了变量 `response` 来存放将要返回的成功响应的数据。接着,在 `response` 上调用 `as_bytes`,因为 `stream` 的 `write` 方法获取一个 `&[u8]` 并直接将这些字节发送给连接。
-
-
-
因为 `write` 操作可能会失败,所以像之前那样对任何错误结果使用 `unwrap`。同理,在真实世界的应用中这里需要添加错误处理。最后,`flush` 会等待并阻塞程序执行直到所有字节都被写入连接中;`TcpStream` 包含一个内部缓冲区来最小化对底层操作系统的调用。
-
-
-
-有了这些修改,运行我们的代码并进行请求!我们不再向终端打印任何数据,所以不会再看到除了 Cargo 以外的任何输出。不过当在浏览器中加载 `127.0.0.1:7878` 时,会得到一个空页面而不是错误。太棒了!我们刚刚手写了一个 HTTP 请求与响应。
+有了这些修改,运行我们的代码并进行请求!我们不再向终端打印任何数据,所以不会再看到除了 Cargo 以外的任何输出。不过当在浏览器中加载 *127.0.0.1:7878* 时,会得到一个空页面而不是错误。太棒了!我们刚刚手写了一个 HTTP 请求与响应。
### 返回真正的 HTML
@@ -263,25 +219,21 @@ small detail I don't think is worth going into in depth. /Carol -->
示例 20-4: 一个简单的 HTML 文件用来作为响应
-这是一个极小化的 HTML 5 文档,它有一个标题和一小段文本。为了在 server 接受请求时返回它,需要如示例 20-5 所示修改 `handle_connection` 来读取 HTML 文件,将其加入到响应的 body 中,并发送:
+这是一个极小化的 HTML5 文档,它有一个标题和一小段文本。为了在 server 接受请求时返回它,需要如示例 20-5 所示修改 `handle_connection` 来读取 HTML 文件,将其加入到响应的 body 中,并发送:
文件名: src/main.rs
```rust
# use std::io::prelude::*;
# use std::net::TcpStream;
-use std::fs::File;
-
+use std::fs;
// --snip--
fn handle_connection(mut stream: TcpStream) {
let mut buffer = [0; 512];
stream.read(&mut buffer).unwrap();
- let mut file = File::open("hello.html").unwrap();
-
- let mut contents = String::new();
- file.read_to_string(&mut contents).unwrap();
+ let contents = fs::read_to_string("hello.html").unwrap();
let response = format!("HTTP/1.1 200 OK\r\n\r\n{}", contents);
@@ -296,20 +248,20 @@ fn handle_connection(mut stream: TcpStream) {
接下来,使用 `format!` 将文件内容加入到将要写入流的成功响应的 body 中。
-使用 `cargo run` 运行程序,在浏览器加载 `127.0.0.1:7878`,你应该会看到渲染出来的 HTML 文件!
+使用 `cargo run` 运行程序,在浏览器加载 *127.0.0.1:7878*,你应该会看到渲染出来的 HTML 文件!
-目前忽略了 `buffer` 中的请求数据并无条件的发送了 HTML 文件的内容。这意味着如果尝试在浏览器中请求 `127.0.0.1:7878/something-else` 也会得到同样的 HTML 响应。如此其作用是非常有限的,也不是大部分 server 所做的;让我们检查请求并只对格式良好(well-formed)的请求 `/` 发送 HTML 文件。
+目前忽略了 `buffer` 中的请求数据并无条件的发送了 HTML 文件的内容。这意味着如果尝试在浏览器中请求 *127.0.0.1:7878/something-else* 也会得到同样的 HTML 响应。如此其作用是非常有限的,也不是大部分 server 所做的;让我们检查请求并只对格式良好(well-formed)的请求 `/` 发送 HTML 文件。
### 验证请求并有选择的进行响应
-目前我们的 web server 不管客户端请求什么都会返回相同的 HTML 文件。让我们增加在返回 HTML 文件前检查浏览器是否请求 `/`,并在其请求任何其他内容时返回错误的功能。为此需要如示例 20-6 那样修改 `handle_connection`。新代码接收到的请求的内容与已知的 `/` 请求的一部分做比较,并增加了 `if` 和 `else` 块来区别处理请求:
+目前我们的 web server 不管客户端请求什么都会返回相同的 HTML 文件。让我们增加在返回 HTML 文件前检查浏览器是否请求 */*,并在其请求任何其他内容时返回错误的功能。为此需要如示例 20-6 那样修改 `handle_connection`。新代码接收到的请求的内容与已知的 */* 请求的一部分做比较,并增加了 `if` 和 `else` 块来区别处理请求:
文件名: src/main.rs
```rust
# use std::io::prelude::*;
# use std::net::TcpStream;
-# use std::fs::File;
+# use std::fs;
// --snip--
fn handle_connection(mut stream: TcpStream) {
@@ -319,48 +271,41 @@ fn handle_connection(mut stream: TcpStream) {
let get = b"GET / HTTP/1.1\r\n";
if buffer.starts_with(get) {
- let mut file = File::open("hello.html").unwrap();
-
- let mut contents = String::new();
- file.read_to_string(&mut contents).unwrap();
+ let contents = fs::read_to_string("hello.html").unwrap();
let response = format!("HTTP/1.1 200 OK\r\n\r\n{}", contents);
stream.write(response.as_bytes()).unwrap();
stream.flush().unwrap();
} else {
- // some other request
+ // 其他请求
}
}
```
示例 20-6: 匹配请求并区别处理 `/` 请求与其他请求
-首先,将与 `/` 请求相关的数据硬编码进变量 `get`。因为我们将原始字节读取进了缓冲区,所以在 `get` 的数据开头增加 `b""` 字节字符串语法将其转换为字节字符串。接着检查 `buffer` 是否以 `get` 中的字节开头。如果是,这就是一个格式良好的 `/` 请求,也就是 `if` 块中期望处理的成功情况,并会返回 HTML 文件内容的代码。
-
+首先,将与 */* 请求相关的数据硬编码进变量 `get`。因为我们将原始字节读取进了缓冲区,所以在 `get` 的数据开头增加 `b""` 字节字符串语法将其转换为字节字符串。接着检查 `buffer` 是否以 `get` 中的字节开头。如果是,这就是一个格式良好的 */* 请求,也就是 `if` 块中期望处理的成功情况,并会返回 HTML 文件内容的代码。
如果 `buffer` **不** 以 `get` 中的字节开头,就说明接收的是其他请求。之后会在 `else` 块中增加代码来响应所有其他请求。
-现在如果运行代码并请求 `127.0.0.1:7878`,就会得到 *hello.html* 中的 HTML。如果进行任何其他请求,比如 `127.0.0.1:7878/something-else`,则会得到像运行示例 20-1 和 20-2 中代码那样的连接错误。
+现在如果运行代码并请求 *127.0.0.1:7878*,就会得到 *hello.html* 中的 HTML。如果进行任何其他请求,比如 *127.0.0.1:7878/something-else*,则会得到像运行示例 20-1 和 20-2 中代码那样的连接错误。
-现在向示例 20-7 的 `else` 块增加代码来返回一个带有 `404` 状态码的响应,这代表了所请求的内容没有找到。接着也会返回一个 HTML 向浏览器终端用户表明此意:
+现在向示例 20-7 的 `else` 块增加代码来返回一个带有 404 状态码的响应,这代表了所请求的内容没有找到。接着也会返回一个 HTML 向浏览器终端用户表明此意:
文件名: src/main.rs
```rust
# use std::io::prelude::*;
# use std::net::TcpStream;
-# use std::fs::File;
+# use std::fs;
# fn handle_connection(mut stream: TcpStream) {
# if true {
// --snip--
} else {
let status_line = "HTTP/1.1 404 NOT FOUND\r\n\r\n";
- let mut file = File::open("404.html").unwrap();
- let mut contents = String::new();
-
- file.read_to_string(&mut contents).unwrap();
+ let contents = fs::read_to_string("404.html").unwrap();
let response = format!("{}{}", status_line, contents);
@@ -370,9 +315,9 @@ fn handle_connection(mut stream: TcpStream) {
# }
```
-示例 20-7: 对于任何不是 `/` 的请求返回 `404` 状态码的响应和错误页面
+示例 20-7: 对于任何不是 */* 的请求返回 `404` 状态码的响应和错误页面
-这里,响应的状态行有状态码 `404` 和原因短语 `NOT FOUND`。仍然没有返回任何 header,而其 body 将是 *404.html* 文件中的 HTML。需要在 *hello.html* 同级目录创建 *404.html* 文件作为错误页面;这一次也可以随意使用任何 HTML 或使用示例 20-8 中的示例 HTML:
+这里,响应的状态行有状态码 404 和原因短语 `NOT FOUND`。仍然没有返回任何 header,而其 body 将是 *404.html* 文件中的 HTML。需要在 *hello.html* 同级目录创建 *404.html* 文件作为错误页面;这一次也可以随意使用任何 HTML 或使用示例 20-8 中的示例 HTML:
文件名: 404.html
@@ -390,9 +335,9 @@ fn handle_connection(mut stream: TcpStream) {