Update some chapters

This commit is contained in:
Aaran Xu 2022-01-31 19:04:04 +08:00
parent a1f1a63faf
commit 5cfcf41b01
5 changed files with 94 additions and 50 deletions

View File

@ -45,9 +45,9 @@
- [将模块分割进不同文件](ch07-05-separating-modules-into-different-files.md)
- [常见集合](ch08-00-common-collections.md)
- [vector](ch08-01-vectors.md)
- [字符串](ch08-02-strings.md)
- [哈希 map](ch08-03-hash-maps.md)
- [使用 vector 存储一列值](ch08-01-vectors.md)
- [使用字符串存储 UTF-8 编码的文本](ch08-02-strings.md)
- [哈希 map 中存储键和关联值](ch08-03-hash-maps.md)
- [错误处理](ch09-00-error-handling.md)
- [`panic!` 与不可恢复的错误](ch09-01-unrecoverable-errors-with-panic.md)
@ -59,9 +59,9 @@
- [trait定义共享的行为](ch10-02-traits.md)
- [生命周期与引用有效性](ch10-03-lifetime-syntax.md)
- [测试](ch11-00-testing.md)
- [编写测试](ch11-01-writing-tests.md)
- [运行测试](ch11-02-running-tests.md)
- [编写自动化测试](ch11-00-testing.md)
- [如何编写测试](ch11-01-writing-tests.md)
- [测试如何测试运行](ch11-02-running-tests.md)
- [测试的组织结构](ch11-03-test-organization.md)
- [一个 I/O 项目:构建命令行程序](ch12-00-an-io-project.md)
@ -88,18 +88,18 @@
- [Cargo 自定义扩展命令](ch14-05-extending-cargo.md)
- [智能指针](ch15-00-smart-pointers.md)
- [`Box<T>` 指向堆上数据,并且可确定大小](ch15-01-box.md)
- [通过 `Deref` trait 将智能指针当作常规引用处理](ch15-02-deref.md)
- [`Drop` Trait 运行清理代码](ch15-03-drop.md)
- [使用 `Box<T>` 指向堆上数据](ch15-01-box.md)
- [使用 `Deref` trait 将智能指针当作常规引用处理](ch15-02-deref.md)
- [使用 `Drop` Trait 运行清理代码](ch15-03-drop.md)
- [`Rc<T>` 引用计数智能指针](ch15-04-rc.md)
- [`RefCell<T>` 与内部可变性模式](ch15-05-interior-mutability.md)
- [引用循环与内存泄漏是安全的](ch15-06-reference-cycles.md)
- [引用循环会导致内存泄漏](ch15-06-reference-cycles.md)
- [无畏并发](ch16-00-concurrency.md)
- [线程](ch16-01-threads.md)
- [消息传递](ch16-02-message-passing.md)
- [共享状态](ch16-03-shared-state.md)
- [可扩展的并发:`Sync` 与 `Send`](ch16-04-extensible-concurrency-sync-and-send.md)
- [使用线程同一时间运行代码](ch16-01-threads.md)
- [使用消息传递在线程间通信](ch16-02-message-passing.md)
- [共享状态并发](ch16-03-shared-state.md)
- [使用`Sync` 与 `Send` Trait 的可扩展并发](ch16-04-extensible-concurrency-sync-and-send.md)
- [Rust 的面向对象编程特性](ch17-00-oop.md)
- [面向对象语言的特点](ch17-01-what-is-oo.md)
@ -111,7 +111,7 @@
- [模式和匹配](ch18-00-patterns.md)
- [所有可能会用到模式的位置](ch18-01-all-the-places-for-patterns.md)
- [Refutability可反驳性: 模式是否会匹配失效](ch18-02-refutability.md)
- [模式的全部语法](ch18-03-pattern-syntax.md)
- [模式语法](ch18-03-pattern-syntax.md)
- [高级特征](ch19-00-advanced-features.md)
- [不安全的 Rust](ch19-01-unsafe-rust.md)
@ -120,9 +120,9 @@
- [高级函数与闭包](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)
- [将单线程 server 变为多线程 server](ch20-02-multithreaded.md)
- [最后的项目: 构建多线程 Web 服务器](ch20-00-final-project-a-web-server.md)
- [构建单线程 Web 服务器](ch20-01-single-threaded.md)
- [将单线程服务器变为多线程服务器](ch20-02-multithreaded.md)
- [优雅停机与清理](ch20-03-graceful-shutdown-and-cleanup.md)
- [附录](appendix-00.md)

View File

@ -185,23 +185,67 @@ error[E0004]: non-exhaustive patterns: `None` not covered
| ^ pattern `None` not covered
```
Rust 知道我们没有覆盖所有可能的情况甚至知道哪些模式被忘记了Rust 中的匹配是 **穷尽的***exhaustive*):必须穷举到最后的可能性来使代码有效。特别的在这个 `Option<T>` 的例子中Rust 防止我们忘记明确的处理 `None` 的情况,这使我们免于假设拥有一个实际上为空的值,这造成了之前提到过的价值亿万的错误
Rust 知道我们没有覆盖所有可能的情况甚至知道哪些模式被忘记了Rust 中的匹配是**穷举式的***exhaustive*):必须穷举到最后的可能性来使代码有效。特别的在这个 `Option<T>` 的例子中Rust 防止我们忘记明确的处理 `None` 的情况,这让我们免于假设拥有一个实际上为空的值,从而使之前提到的价值亿万的错误不可能发生
### `_` 通配
### 通配模式和 `_` 占位
Rust 也提供了一个模式用于不想列举出所有可能值的场景。例如,`u8` 可以拥有 0 到 255 的有效的值,如果我们只关心 1、3、5 和 7 这几个值,就并不想必须列出 0、2、4、6、8、9 一直到 255 的值。所幸我们不必这么做:可以使用特殊的模式 `_` 替代
让我们看一个例子,我们希望对一些特定的值采取特殊操作,而对其他的值采取默认操作。想象我们正在玩一个游戏,如果你掷出骰子的值为 3角色不会移动而是会得到一顶新奇的帽子。如果你掷出了 7你的角色将失去新奇的帽子。对于其他的数值你的角色会在棋盘上移动相应的格子。这是一个实现了上述逻辑的 `match`,骰子的结果是硬编码而不是一个随机值,其他的逻辑部分使用了没有函数体的函数来表示,实现它们超出了本例的范围
```rust
let some_u8_value = 0u8;
match some_u8_value {
1 => println!("one"),
3 => println!("three"),
5 => println!("five"),
7 => println!("seven"),
_ => (),
let dice_roll = 9;
match dice_roll {
3 => add_fancy_hat(),
7 => remove_fancy_hat(),
other => move_player(other),
}
fn add_fancy_hat() {}
fn remove_fancy_hat() {}
fn move_player(num_spaces: u8) {}
```
`_` 模式会匹配所有的值。通过将其放置于其他分支之后,`_` 将会匹配所有之前没有指定的可能的值。`()` 就是 unit 值,所以 `_` 的情况什么也不会发生。因此,可以说我们想要对 `_` 通配符之前没有列出的所有可能的值不做任何处理。
+对于前两个分支,匹配模式是字面值 3 和 7最后一个分支则涵盖了所有其他可能的值模式是我们命名为 `other` 的一个变量。`other` 分支的代码通过将其传递给 `move_player` 函数来使用这个变量
然而,`match` 在只关心 **一种** 情况的场景中可能就有点啰嗦了。为此 Rust 提供了 `if let`
即使我们没有列出 `u8` 所有可能的值,这段代码依然能够编译,因为最后一个模式将匹配所有未被特殊列出的值。这种通配模式满足了 `match` 必须被穷尽的要求。请注意我们必须将通配分支放在最后因为模式是按顺序匹配的。如果我们在通配分支后添加其他分支Rust 将会警告我们,因为此后的分支永远不会被匹配到。
Rust 还提供了一个模式,当我们不想使用通配模式获取的值时,请使用 `_` ,这是一个特殊的模式,可以匹配任意值而不绑定到该值。这告诉 Rust 我们不会使用这个值,所以 Rust 也不会警告我们存在未使用的变量。
让我们改变游戏规则,当你掷出的值不是 3 或 7 的时候,你必须再次掷出。这种情况下我们不需要使用这个值,所以我们改动代码使用 `_` 来替代变量 `other`
```rust
let dice_roll = 9;
match dice_roll {
3 => add_fancy_hat(),
7 => remove_fancy_hat(),
_ => reroll(),
}
fn add_fancy_hat() {}
fn remove_fancy_hat() {}
fn reroll() {}
```
这个例子也满足穷举性要求,因为我们在最后一个分支中明确地忽略了其他的值。我们没有忘记处理任何东西。
让我们再次改变游戏规则,如果你掷出 3 或 7 以外的值,你的回合将无事发生。我们可以使用单元值(在[“元组类型”][tuples]<!-- ignore -->一节中提到的空元组)作为 `_` 分支的代码:
[tuples]: ch03-02-data-types.html#元组类型
```rust
let dice_roll = 9;
match dice_roll {
3 => add_fancy_hat(),
7 => remove_fancy_hat(),
_ => (),
}
fn add_fancy_hat() {}
fn remove_fancy_hat() {}
```
在这里,我们明确告诉 Rust 我们不会使用与前面模式不匹配的值,并且这种情况下我们不想运行任何代码。
我们将在[第 18 章][ch18-00-patterns]<!-- ignore -->中介绍更多关于模式和匹配的内容。现在,让我们继续讨论 `if let` 语法,这在 `match` 表达式有点啰嗦的情况下很有用。
[tuples]: ch03-02-data-types.html#元组类型
[ch18-00-patterns]: ch18-00-patterns.html

View File

@ -169,17 +169,17 @@ impl State for PendingReview {
这里为 `Post` 增加一个获取 `self` 可变引用的公有方法 `request_review`。接着在 `Post` 的当前状态下调用内部的 `request_review` 方法,并且第二个 `request_review` 方法会消费当前的状态并返回一个新状态。
这里给 `State` trait 增加了 `request_review` 方法;所有实现了这个 trait 的类型现在都需要实现 `request_review` 方法。注意不同于使用 `self``&self` 或者 `&mut self` 作为方法的第一个参数,这里使用了 `self: Box<Self>`。这个语法意味着这个方法调用只对这个类型的 `Box` 有效。这个语法获取了 `Box<Self>` 的所有权使老状态无效化以便 `Post` 的状态值可以将自身转换为新状态。
这里给 `State` trait 增加了 `request_review` 方法;所有实现了这个 trait 的类型现在都需要实现 `request_review` 方法。注意不同于使用 `self``&self` 或者 `&mut self` 作为方法的第一个参数,这里使用了 `self: Box<Self>`。这个语法意味着该方法只可在持有这个类型的 `Box` 上被调用。这个语法获取了 `Box<Self>` 的所有权使老状态无效化以便 `Post` 的状态值可转换为一个新状态。
为了消费老状态,`request_review` 方法需要获取状态值的所有权。这就是 `Post``state` 字段中 `Option` 的来历:调用 `take` 方法将 `state` 字段中的 `Some` 值取出并留下一个 `None`,因为 Rust 不允许结构体中存在空的字段。这使得我们将 `state` 值移`Post` 而不是借用它。接着将博文的 `state` 值设置为这个操作的结果。
为了消费老状态,`request_review` 方法需要获取状态值的所有权。这就是 `Post``state` 字段中 `Option` 的来历:调用 `take` 方法将 `state` 字段中的 `Some` 值取出并留下一个 `None`,因为 Rust 不允许结构体实例中存在值为空的字段。这使得我们将 `state` 值移出 `Post` 而不是借用它。接着我们将博文的 `state` 值设置为这个操作的结果。
这里需要将 `state` 临时设置为 `None`,不同于像 `self.state = self.state.request_review();` 这样的代码直接设置 `state` 字段,来获取 `state` 值的所有权。这确保了当 `Post` 被转换为新状态后能使用老 `state` 值。
我们需要将 `state` 临时设置为 `None` 来获取 `state` 值,即老状态的所有权,而不是使用 `self.state = self.state.request_review();` 这样的代码直接更新状态值。这确保了当 `Post` 被转换为新状态后不能使用老 `state` 值。
`Draft`方法 `request_review` 的实现返回一个新的,装箱的 `PendingReview` 结构体的实例,其用来代表博文处于等待审核状态。结构体 `PendingReview` 同样也实现了 `request_review` 方法,不过它不进行任何状态转换。相反它返回自身,因为请求审核已经处于 `PendingReview` 状态的博文应该保持 `PendingReview` 状态。
`Draft` `request_review` 方法需要返回一个新的,装箱的 `PendingReview` 结构体的实例,其用来代表博文处于等待审核状态。结构体 `PendingReview` 同样也实现了 `request_review` 方法,不过它不进行任何状态转换。相反它返回自身,因为当我们请求审核一个已经处于 `PendingReview` 状态的博文,它应该继续保持 `PendingReview` 状态。
现在开始能够看出状态模式的优势了:`Post` 的 `request_review` 方法无论 `state` 是何值都是一样的。每个状态只负责它自己的规则。
现在我们能看出状态模式的优势了:无论 `state` 是何值,`Post` 的 `request_review` 方法都是一样的。每个状态只负责它自己的规则。
我们将继续保持 `Post``content` 方法不变,返回一个空字符串 slice。现在可以拥有 `PendingReview` 状态而不仅仅是 `Draft` 状态的 `Post` 了,不过我们希望在 `PendingReview` 状态下也有相同的行为。现在示例 17-11 中直到 10 行的代码是可以执行的!
我们将继续保持 `Post``content` 方法实现不变,返回一个空字符串 slice。现在我们可以拥有 `PendingReview` 状态和 `Draft` 状态的 `Post` 了,不过我们希望在 `PendingReview` 状态下 `Post` 也有相同的行为。现在示例 17-11 中直到 10 行的代码是可以执行的!
### 增加改变 `content` 行为的 `approve` 方法

View File

@ -2,13 +2,13 @@
这是一次漫长的旅途,不过我们到达了本书的结束。在本章中,我们将一同构建另一个项目,来展示最后几章所学,同时复习更早的章节。
作为最后的项目,我们将要实现一个返回 “hello” 的 web server,它在浏览器中看起来就如图例 20-1 所示:
作为最后的项目,我们将要实现一个返回 “hello” 的 Web 服务器,它在浏览器中看起来就如图例 20-1 所示:
![hello from rust](img/trpl20-01.png)
<span class="caption">图例 20-1: 我们最后将一起分享的项目</span>
如下是我们将怎样构建此 web server 的计划:
如下是我们将怎样构建此 Web 服务器的计划:
1. 学习一些 TCP 与 HTTP 知识
2. 在套接字socket上监听 TCP 请求
@ -16,6 +16,6 @@
4. 创建一个合适的 HTTP 响应
5. 通过线程池改善 server 的吞吐量
不过在开始之前,需要提到一点细节:这里使用的方法并不是使用 Rust 构建 web server 最好的方法。[crates.io](https://crates.io/) 上有很多可用于生产环境的 crate它们提供了比我们所要编写的更为完整的 web server 和线程池实现。
不过在开始之前,需要提到一点细节:这里使用的方法并不是使用 Rust 构建 Web 服务器最好的方法。[crates.io](https://crates.io/) 上有很多可用于生产环境的 crate它们提供了比我们所要编写的更为完整的 Web 服务器和线程池实现。
然而,本章的目的在于学习,而不是走捷径。因为 Rust 是一个系统编程语言,我们能够选择处理什么层次的抽象,并能够选择比其他语言可能或可用的层次更低的层次。因此我们将自己编写一个基础的 HTTP server 和线程池,以便学习将来可能用到的 crate 背后的通用理念和技术。

View File

@ -1,10 +1,10 @@
## 将单线程服务器变为多线程服务器
目前 server 会依次处理每一个请求,意味着它在完成第一个连接的处理之前不会处理第二个连接。如果 server 正接收越来越多的请求,这类串行操作会使性能越来越差。如果一个请求花费很长时间来处理,随后而来的请求则不得不等待这个长请求结束,即便这些新请求可以很快就处理完。我们需要修复这种情况,不过首先让我们实际尝试一下这个问题。
目前服务器会依次处理每一个请求,意味着它在完成第一个连接的处理之前不会处理第二个连接。如果服务器正接收越来越多的请求,这类串行操作会使性能越来越差。如果一个请求花费很长时间来处理,随后而来的请求则不得不等待这个长请求结束,即便这些新请求可以很快就处理完。我们需要修复这种情况,不过首先让我们实际尝试一下这个问题。
### 在当前 server 实现中模拟慢请求
### 在当前服务器实现中模拟慢请求
让我们看看一个慢请求如何影响当前 server 实现中的其他请求。示例 20-10 通过模拟慢响应实现了 */sleep* 请求处理,它会使 server 在响应之前休眠五秒。
让我们看看一个慢请求如何影响当前服务器实现中的其他请求。示例 20-10 通过模拟慢响应实现了 */sleep* 请求处理,它会使服务器在响应之前休眠五秒。
<span class="filename">文件名: src/main.rs</span>
@ -41,21 +41,21 @@ fn handle_connection(mut stream: TcpStream) {
这段代码有些凌乱,不过对于模拟的目的来说已经足够。这里创建了第二个请求 `sleep`,我们会识别其数据。在 `if` 块之后增加了一个 `else if` 来检查 */sleep* 请求,当接收到这个请求时,在渲染成功 HTML 页面之前会先休眠五秒。
现在就可以真切的看出我们的 server 有多么的原始:真实的库将会以更简洁的方式处理多请求识别问题!
现在就可以真切的看出我们的服务器有多么的原始:真实的库将会以更简洁的方式处理多请求识别问题!
使用 `cargo run` 启动 server并接着打开两个浏览器窗口一个请求 *http://127.0.0.1:7878/* 而另一个请求 *http://127.0.0.1:7878/sleep* 。如果像之前一样多次请求 */*,会发现响应的比较快速。不过如果请求 */sleep* 之后在请求 */*,就会看到 */* 会等待直到 `sleep` 休眠完五秒之后才出现。
这里有多种办法来改变我们的 web server 使其避免所有请求都排在慢请求之后;我们将要实现的一个便是线程池。
这里有多种办法来改变我们的 Web 服务器使其避免所有请求都排在慢请求之后;我们将要实现的一个便是线程池。
### 使用线程池改善吞吐量
**线程池***thread pool*)是一组预先分配的等待或准备处理任务的线程。当程序收到一个新任务,线程池中的一个线程会被分配任务,这个线程会离开并处理任务。其余的线程则可用于处理在第一个线程处理任务的同时处理其他接收到的任务。当第一个线程处理完任务时,它会返回空闲线程池中等待处理新任务。线程池允许我们并发处理连接,增加 server 的吞吐量。
**线程池***thread pool*)是一组预先分配的等待或准备处理任务的线程。当程序收到一个新任务,线程池中的一个线程会被分配任务,这个线程会离开并处理任务。其余的线程则可用于处理在第一个线程处理任务的同时处理其他接收到的任务。当第一个线程处理完任务时,它会返回空闲线程池中等待处理新任务。线程池允许我们并发处理连接,增加服务器的吞吐量。
我们会将池中线程限制为较少的数量以防拒绝服务Denial of Service DoS攻击如果程序为每一个接收的请求都新建一个线程某人向 server 发起千万级的请求时会耗尽服务器的资源并导致所有请求的处理都被终止。
我们会将池中线程限制为较少的数量以防拒绝服务Denial of Service DoS攻击如果程序为每一个接收的请求都新建一个线程某人向服务器发起千万级的请求时会耗尽服务器的资源并导致所有请求的处理都被终止。
不同于分配无限的线程,线程池中将有固定数量的等待线程。当新进请求时,将请求发送到线程池中做处理。线程池会维护一个接收请求的队列。每一个线程会从队列中取出一个请求,处理请求,接着向对队列索取另一个请求。通过这种设计,则可以并发处理 `N` 个请求,其中 `N` 为线程数。如果每一个线程都在响应慢请求,之后的请求仍然会阻塞队列,不过相比之前增加了能处理的慢请求的数量。
这个设计仅仅是多种改善 web server 吞吐量的方法之一。其他可供探索的方法有 fork/join 模型和单线程异步 I/O 模型。如果你对这个主题感兴趣,则可以阅读更多关于其他解决方案的内容并尝试用 Rust 实现他们;对于一个像 Rust 这样的底层语言,所有这些方法都是可能的。
这个设计仅仅是多种改善 Web 服务器吞吐量的方法之一。其他可供探索的方法有 fork/join 模型和单线程异步 I/O 模型。如果你对这个主题感兴趣,则可以阅读更多关于其他解决方案的内容并尝试用 Rust 实现他们;对于一个像 Rust 这样的底层语言,所有这些方法都是可能的。
在开始之前,让我们讨论一下线程池应用看起来怎样。当尝试设计代码时,首先编写客户端接口确实有助于指导代码设计。以期望的调用方式来构建 API 代码的结构,接着在这个结构之内实现功能,而不是先实现功能再设计公有 API。
@ -145,7 +145,7 @@ error[E0433]: failed to resolve. Use of undeclared type or module `ThreadPool`
error: aborting due to previous error
```
好的,这告诉我们需要一个 `ThreadPool` 类型或模块,所以我们将构建一个。`ThreadPool` 的实现会与 web server 的特定工作相独立,所以让我们从 `hello` crate 切换到存放 `ThreadPool` 实现的新库 crate。这也意味着可以在任何工作中使用这个单独的线程池库而不仅仅是处理网络请求。
好的,这告诉我们需要一个 `ThreadPool` 类型或模块,所以我们将构建一个。`ThreadPool` 的实现会与 Web 服务器的特定工作相独立,所以让我们从 `hello` crate 切换到存放 `ThreadPool` 实现的新库 crate。这也意味着可以在任何工作中使用这个单独的线程池库而不仅仅是处理网络请求。
创建 *src/lib.rs* 文件,它包含了目前可用的最简单的 `ThreadPool` 定义:
@ -753,9 +753,9 @@ Worker 0 got a job; executing.
Worker 2 got a job; executing.
```
成功了!现在我们有了一个可以异步执行连接的线程池!它绝不会创建超过四个线程,所以当 server 收到大量请求时系统也不会负担过重。如果请求 */sleep*server 也能够通过另外一个线程处理其他请求。
成功了!现在我们有了一个可以异步执行连接的线程池!它绝不会创建超过四个线程,所以当服务器收到大量请求时系统也不会负担过重。如果请求 */sleep*server 也能够通过另外一个线程处理其他请求。
> 注意如果同时在多个浏览器窗口打开 */sleep*,它们可能会彼此间隔地加载 5 秒,因为一些浏览器处于缓存的原因会顺序执行相同请求的多个实例。这些限制并不是由于我们的 web server 造成的。
> 注意如果同时在多个浏览器窗口打开 */sleep*,它们可能会彼此间隔地加载 5 秒,因为一些浏览器处于缓存的原因会顺序执行相同请求的多个实例。这些限制并不是由于我们的 Web 服务器造成的。
在学习了第 18 章的 `while let` 循环之后,你可能会好奇为何不能如此编写 worker 线程,如示例 20-21 所示: