diff --git a/src/ch01-02-hello-world.md b/src/ch01-02-hello-world.md index 39286ff..baec2d5 100644 --- a/src/ch01-02-hello-world.md +++ b/src/ch01-02-hello-world.md @@ -38,7 +38,9 @@ $ cd hello_world 现在打开刚创建的 *main.rs* 文件,输入示例 1-1 中的代码。 -+
+ +文件名:main.rs ```rust fn main() { @@ -46,7 +48,10 @@ fn main() { } ``` -
+
示例 1-1:一个打印 `Hello, world!` 的程序
+ + + 保存文件,并回到当前目录为“~/projects/hello_world”的终端窗口。在 Linux 或 macOS 上,输入如下命令,编译并运行文件: diff --git a/src/ch01-03-hello-cargo.md b/src/ch01-03-hello-cargo.md index b68fe79..646c65b 100644 --- a/src/ch01-03-hello-cargo.md +++ b/src/ch01-03-hello-cargo.md @@ -35,7 +35,9 @@ $ cd hello_cargo 请自行选用文本编辑器打开 *Cargo.toml* 文件。它应该看起来如示例 1-2 所示: -+
+ +文件名:Cargo.toml ```toml [package] @@ -48,7 +50,9 @@ edition = "2021" [dependencies] ``` -
+
示例 1-2:*cargo new* 命令生成的 *Cargo.toml* 的内容
+ + 这个文件使用 [*TOML*][toml] (*Tom's Obvious, Minimal Language*) 格式,这是 Cargo 配置文件的格式。 diff --git a/src/ch02-00-guessing-game-tutorial.md b/src/ch02-00-guessing-game-tutorial.md index 1fdc861..945b744 100644 --- a/src/ch02-00-guessing-game-tutorial.md +++ b/src/ch02-00-guessing-game-tutorial.md @@ -49,13 +49,17 @@ $ cd guessing_game 猜数字程序的第一部分请求和处理用户输入,并检查输入是否符合预期的格式。首先,我们会允许玩家输入一个猜测。在 _src/main.rs_ 中输入示例 2-1 中的代码。 -+
+ +文件名:src/main.rs ```rust,ignore {{#rustdoc_include ../listings/ch02-guessing-game-tutorial/listing-02-01/src/main.rs:all}} ``` -
+
示例 2-1:获取用户猜测并打印的代码
+ + 这些代码包含很多信息,我们一行一行地过一遍。为了获取用户输入并打印结果作为输出,我们需要将 `io` 输入/输出库引入当前作用域。`io` 库来自于标准库,也被称为 `std`: @@ -219,7 +223,7 @@ Cargo 认为这些版本与 `0.8.5` 版本的公有 API 相兼容,这样的版 现在,不修改任何代码,构建项目,如示例 2-2 所示。 -+
```console $ cargo build @@ -242,7 +246,9 @@ $ cargo build Finished dev [unoptimized + debuginfo] target(s) in 2.53s ``` -
+
示例 2-2:将 rand crate 添加为依赖之后运行 `cargo build` 的输出
+ + 可能会出现不同的版本号(多亏了语义化版本,它们与代码是兼容的!),并且显示的行数可能会有所不同(取决于操作系统),行的顺序也可能会不同。 @@ -293,13 +299,17 @@ rand = "0.9.0" 让我们开始使用 `rand` 来生成一个猜数字随机数。下一步是更新 *src/main.rs*,如示例 2-3 所示。 -+
+ +文件名:src/main.rs ```rust,ignore {{#rustdoc_include ../listings/ch02-guessing-game-tutorial/listing-02-03/src/main.rs:all}} ``` -
+
示例 2-3:添加生成随机数的代码
+ + 首先,我们新增了一行 `use rand::Rng;`。`Rng` 是一个 trait,它定义了随机数生成器应实现的方法,想使用这些方法的话,此 trait 必须在作用域中。第十章会详细介绍 trait。 @@ -338,13 +348,17 @@ You guessed: 5 现在有了用户输入和一个随机数,我们可以比较它们。这个步骤如示例 2-4 所示。注意这段代码还不能通过编译,我们稍后会解释。 -+
+ +文件名:src/main.rs ```rust,ignore,does_not_compile {{#rustdoc_include ../listings/ch02-guessing-game-tutorial/listing-02-04/src/main.rs:here}} ``` -
+
示例 2-4:处理比较两个数字可能的返回值
+ + 首先我们增加了另一个 `use` 声明,从标准库引入了一个叫做 `std::cmp::Ordering` 的类型到作用域中。 `Ordering` 也是一个枚举,不过它的成员是 `Less`、`Greater` 和 `Equal`。这是比较两个值时可能出现的三种结果。 @@ -464,13 +478,17 @@ note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace 为了进一步改善游戏性,不要在用户输入非数字时崩溃,需要忽略非数字,让用户可以继续猜测。可以通过修改 `guess` 将 `String` 转化为 `u32` 那部分代码来实现,如示例 2-5 所示: -+
+ +文件名:src/main.rs ```rust,ignore {{#rustdoc_include ../listings/ch02-guessing-game-tutorial/listing-02-05/src/main.rs:here}} ``` -
+
示例 2-5:忽略非数字的猜测并重新请求数字而不是让程序崩溃
+ + 我们将 `expect` 调用换成 `match` 语句,以从遇到错误就崩溃转换为处理错误。须知 `parse` 返回一个 `Result` 类型,而 `Result` 是一个拥有 `Ok` 或 `Err` 成员的枚举。这里使用的 `match` 表达式,和之前处理 `cmp` 方法返回 `Ordering` 时用的一样。 @@ -505,13 +523,17 @@ You win! 太棒了!再有最后一个小的修改,就能完成猜数字游戏了:还记得程序依然会打印出秘密数字。在测试时还好,但正式发布时会毁了游戏。删掉打印秘密数字的 `println!`。示例 2-6 为最终代码: -+
+ +文件名:src/main.rs ```rust,ignore {{#rustdoc_include ../listings/ch02-guessing-game-tutorial/listing-02-06/src/main.rs}} ``` -
+
示例 2-6:猜数字游戏的完整代码
+ + 此时此刻,你顺利完成了猜数字游戏。恭喜! diff --git a/src/ch17-00-async-await.md b/src/ch17-00-async-await.md index cc67cc0..c4b1fde 100644 --- a/src/ch17-00-async-await.md +++ b/src/ch17-00-async-await.md @@ -14,7 +14,7 @@ 在上述两个例子中,操作系统的隐式中断提供了一种形式的并发。不过这种并发只发生在整个程序的级别:操作系统中断一个程序并让其它程序继续处理。在很多场景中,因为我们能比操作系统在更为细的粒度上理解我们的程序,所以我们可以观察到很多操作系统无法察觉的并发机会。 -例如,如果我们在构建一个管理文件下载的工具,我们应该以一种开始一个下载任务时不会锁定 UI 的方式来编写程序,并且用户应该能够同时开始多个下载。不过很多操作系统与网络交互的 API 都是 *阻塞* 的 (*blocking*)。也就是说这些 API 在数据完全就绪之前阻塞程序的运行。 +例如,如果我们在构建一个管理文件下载的工具,我们应该以一种开始一个下载任务时不会锁定 UI 的方式来编写程序,并且用户应该能够同时开始多个下载。不过很多操作系统与网络交互的 API 都是 *阻塞* 的(*blocking*)。也就是说这些 API 在数据完全就绪之前阻塞程序的运行。 > 注意:如果你仔细思索一下,会发现这是 *大部分* 函数调用的工作方式!不过我们通常将 “阻塞” 这个术语保留给那些与文件、网络或其它计算机资源交互的函数调用,因为这些是单个程序可以从 *非* 阻塞操作中获益的地方。 @@ -48,10 +48,32 @@ println!("{data}");
-并行工作流 +并发工作流
图 17-2:一个并行流,其中任务 A 和任务 B 的工作同时独立进行
-在这两种场景中,你可能需要协调不同的任务。 +在这两种场景中,你可能需要协调不同的任务。可能你 *认为* 某个人所作的任务与其他人的工作完全不相关,不过它确实需要其他组员的工作完成才能进行。一些工作可以并行进行,不过一些工作事实上是 *串行* 的:它们只能串行地发生,一个接着一个,如图 17-3 所示。 + +
+ +并发工作流 + +
图 17-3:一个部分并行的工作流,其中任务 A 和任务 B 的工作相互独立,直到任务 A3 阻塞在等待任务 B3 的结果
+ +
+ +同理,你可能会意识到你自己的一个任务依赖另一个任务。现在并发任务也变成串行的了。 + +并行与并发也可以相互交叉(阻塞)。如果你得知某个同事卡在等待你的一个任务完成,你可能会集中所有精力在这个任务上来 “解锁” 你的同事。你和你的同事则不再能并行地工作了,同时你也不能够并发地处理自己的任务。 + +同样的基础动态也作用于软件与硬件。在一个单核的机器上,CPU 一次只能做一个操作,不过它仍然可以并发工作。使用像线程、进程和异步(async)这样的工具,计算机可以暂停一个活动,并在最终切换回第一个活动之前切换到其它活动。在一个有多个 CPU 核心的机器上,它也可以并行工作。一个核心可以做一件工作的同时另一个核心可以做一些完全不相关的工作,而且这些工作实际上是同时发生的。 + +当使用 Rust 中的 async 时,我们已经在处理并发。取决于硬件、操作系统和所使用的异步运行时(async runtime)-- 之后会介绍更多的异步运行时!并发也可能在底层使用了并行。 + +现在让我们深入理解 Rust 的异步编程实际上是如何工作的!在剩下的章节,我们会: + +- 学习如何使用 Rust 的 `async` 和 `await` 语法 +- 探索如何使用异步模型来解决第十六章中遇到的一些挑战 +- 了解多线程和异步如何互补,在很多场景中你甚至可以同时使用两者