Fix texts by use autocorrect --fix.

This commit is contained in:
Jason Lee 2023-01-17 11:38:33 +08:00
parent 23673cb6e7
commit 483ea9f308
85 changed files with 513 additions and 513 deletions

View File

@ -1,4 +1,4 @@
# Rust 程序设计语言2021 edition 施工中) 简体中文版
# Rust 程序设计语言2021 edition 施工中)简体中文版
![Build Status](https://github.com/KaiserY/trpl-zh-cn/workflows/CI/badge.svg)
@ -43,11 +43,11 @@ vuepress dev ./src
## 社区资源
- Rust语言中文社区<https://rust.cc/>
- Rust 语言中文社区:<https://rust.cc/>
- Rust 中文 Wiki<https://wiki.rust-china.org/>
- Rust编程语言社区1群群号303838735已满只能内部邀请
- Rust编程语言社区2群群号813448660
- Rust水群(编程社区子群),电报群:[t.me/rust_deep_water](//t.me/rust_deep_water)
- Rust 编程语言社区 1 群号303838735已满只能内部邀请
- Rust 编程语言社区 2 群号813448660
- Rust 水群 (编程社区子群),电报群:[t.me/rust_deep_water](//t.me/rust_deep_water)
## PDF

View File

@ -1,6 +1,6 @@
fn main() {
// ANCHOR: here
{ // s 在这里无效, 它尚未声明
{ // s 在这里无效它尚未声明
let s = "hello"; // 从此处起s 是有效的
// 使用 s

View File

@ -10,7 +10,7 @@ fn main() {
// 但 i32 是 Copy 的,
// 所以在后面可继续使用 x
} // 这里, x 先移出了作用域,然后是 s。但因为 s 的值已被移走,
} // 这里x 先移出了作用域,然后是 s。但因为 s 的值已被移走,
// 没有特殊之处
fn takes_ownership(some_string: String) { // some_string 进入作用域

View File

@ -5,16 +5,16 @@ fn main() {
let s2 = String::from("hello"); // s2 进入作用域
let s3 = takes_and_gives_back(s2); // s2 被移动到
// takes_and_gives_back 中,
// takes_and_gives_back 中
// 它也将返回值移给 s3
} // 这里, s3 移出作用域并被丢弃。s2 也移出作用域,但已被移走,
} // 这里s3 移出作用域并被丢弃。s2 也移出作用域,但已被移走,
// 所以什么也不会发生。s1 离开作用域并被丢弃
fn gives_ownership() -> String { // gives_ownership 会将
// 返回值移动给
// 调用它的函数
let some_string = String::from("yours"); // some_string 进入作用域.
let some_string = String::from("yours"); // some_string 进入作用域
some_string // 返回 some_string
// 并移出给调用的函数

View File

@ -7,7 +7,7 @@ fn main() {
}
// ANCHOR: here
fn calculate_length(s: &String) -> usize { // s是String的引用
fn calculate_length(s: &String) -> usize { // s String 的引用
s.len()
} // 这里s 离开了作用域。但因为它并不拥有引用值的所有权,
// 所以什么也不会发生

View File

@ -16,7 +16,7 @@ fn main() {
let word = first_word(&s);
s.clear(); // 错误!
s.clear(); // 错误
println!("the first word is: {}", word);
}

View File

@ -120,7 +120,7 @@
- [高级函数与闭包](ch19-05-advanced-functions-and-closures.md)
- [](ch19-06-macros.md)
- [最后的项目: 构建多线程 web server](ch20-00-final-project-a-web-server.md)
- [最后的项目构建多线程 web server](ch20-00-final-project-a-web-server.md)
- [建立单线程 web server](ch20-01-single-threaded.md)
- [将单线程 server 变为多线程 server](ch20-02-multithreaded.md)
- [优雅停机与清理](ch20-03-graceful-shutdown-and-cleanup.md)

View File

@ -120,7 +120,7 @@
- [高级函数与闭包](ch19-05-advanced-functions-and-closures.md)
- [](ch19-06-macros.md)
- [最后的项目: 构建多线程 web server](ch20-00-final-project-a-web-server.md)
- [最后的项目构建多线程 web server](ch20-00-final-project-a-web-server.md)
- [建立单线程 web server](ch20-01-single-threaded.md)
- [将单线程 server 变为多线程 server](ch20-02-multithreaded.md)
- [优雅停机与清理](ch20-03-graceful-shutdown-and-cleanup.md)

View File

@ -97,7 +97,7 @@ error: expected identifier, found keyword `match`
该错误表示你不能将关键字 `match` 用作函数标识符。你可以使用原始标识符将 `match` 作为函数名称使用:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
fn r#match(needle: &str, haystack: &str) -> bool {
@ -111,6 +111,6 @@ fn main() {
此代码编译没有任何错误。注意 `r#` 前缀需同时用于函数名定义和 `main` 函数中的调用。
原始标识符允许使用你选择的任何单词作为标识符,即使该单词恰好是保留关键字。 这给予了我们更大的自由来选择名字,这样与其他语言交互式就不用考虑到关键字问题,在要交互的语言中这个名字不是关键字。此外,原始标识符允许你使用以不同于你的 crate 使用的 Rust 版本编写的库。比如,`try` 在 2015 edition 中不是关键字,而在 2018 edition 则是。所以如果用 2015 edition 编写的库中带有 `try` 函数,在 2018 edition 中调用时就需要使用原始标识符语法,在这里是 `r#try`。有关版本的更多信息,请参见[附录 E][appendix-e]。
原始标识符允许使用你选择的任何单词作为标识符,即使该单词恰好是保留关键字。这给予了我们更大的自由来选择名字,这样与其他语言交互式就不用考虑到关键字问题,在要交互的语言中这个名字不是关键字。此外,原始标识符允许你使用以不同于你的 crate 使用的 Rust 版本编写的库。比如,`try` 在 2015 edition 中不是关键字,而在 2018 edition 则是。所以如果用 2015 edition 编写的库中带有 `try` 函数,在 2018 edition 中调用时就需要使用原始标识符语法,在这里是 `r#try`。有关版本的更多信息,请参见[附录 E][appendix-e]。
[appendix-e]: appendix-05-editions.html

View File

@ -33,7 +33,7 @@
| `+=` | `var += expr` | 算术加法与赋值 | `AddAssign` |
| `,` | `expr, expr` | 参数以及元素分隔符 | |
| `-` | `- expr` | 算术取负 | `Neg` |
| `-` | `expr - expr` | 算术减法| `Sub` |
| `-` | `expr - expr` | 算术减法 | `Sub` |
| `-=` | `var -= expr` | 算术减法与赋值 | `SubAssign` |
| `->` | `fn(...) -> type`, <code>&vert;...&vert; -> type</code> | 函数与闭包,返回类型 | |
| `.` | `expr.ident` | 成员访问 | |
@ -41,7 +41,7 @@
| `..=` | `..=expr`, `expr..=expr` | 右闭区间范围模式 | `PartialOrd` |
| `..` | `..expr` | 结构体更新语法 | |
| `..` | `variant(x, ..)`, `struct_type { x, .. }` | “与剩余部分” 的模式绑定 | |
| `...` | `expr...expr` | Deprecated请使用 `..=`)在模式中: 闭区间范围模式 | |
| `...` | `expr...expr` | Deprecated请使用 `..=`)在模式中闭区间范围模式 | |
| `/` | `expr / expr` | 算术除法 | `Div` |
| `/=` | `var /= expr` | 算术除法与赋值 | `DivAssign` |
| `:` | `pat: type`, `ident: type` | 约束 | |
@ -50,7 +50,7 @@
| `;` | `expr;` | 语句和语句结束符 | |
| `;` | `[...; len]` | 固定大小数组语法的部分 | |
| `<<` | `expr << expr` |左移 | `Shl` |
| `<<=` | `var <<= expr` | 左移与赋值| `ShlAssign` |
| `<<=` | `var <<= expr` | 左移与赋值 | `ShlAssign` |
| `<` | `expr < expr` | 小于比较 | `PartialOrd` |
| `<=` | `expr <= expr` | 小于等于比较 | `PartialOrd` |
| `=` | `var = expr`, `ident = type` | 赋值/等值 | |
@ -102,7 +102,7 @@
| `self::path` | 与当前模块相对的路径(如一个显式相对路径)|
| `super::path` | 与父模块相对的路径 |
| `type::ident`, `<type as trait>::ident` | 关联常量、函数以及类型 |
| `<type>::...` | 不可以被直接命名的关联项类型(如 `<&T>::...``<[T]>::...` 等) |
| `<type>::...` | 不可以被直接命名的关联项类型(如 `<&T>::...``<[T]>::...`,等) |
| `trait::method(...)` | 通过命名定义的 trait 来消除方法调用的二义性 |
| `type::method(...)` | 通过命名定义的类型来消除方法调用的二义性 |
| `<type as trait>::method(...)` | 通过命名 trait 和类型来消除方法调用的二义性 |
@ -195,5 +195,5 @@
| `[...]` | 数组 |
| `[expr; len]` | 复制了 `len``expr`的数组 |
| `[type; len]` | 包含 `len``type` 类型的数组|
| `expr[expr]` | 集合索引。 重载(`Index`, `IndexMut` |
| `expr[expr]` | 集合索引。重载(`Index`, `IndexMut` |
| `expr[..]`, `expr[a..]`, `expr[..b]`, `expr[a..b]` | 集合索引,使用 `Range``RangeFrom``RangeTo` 或 `RangeFull` 作为索引来代替集合 slice |

View File

@ -52,7 +52,7 @@
例如,对于来自于 `rand` crate 中的 `gen_range` 方法来说,当在一个范围表达式指定的范围内生成一个随机值时,`PartialOrd` trait 是必须的。
`Ord` trait 也让你明白在一个带注解类型上的任意两个值存在有效顺序。`Ord` trait 实现了 `cmp` 方法,它返回一个 `Ordering` 而不是 `Option<Ordering>`,因为总存在一个合法的顺序。只可以在实现了 `PartialOrd``Eq``Eq` 依赖 `PartialEq`)的类型上使用 `Ord` trait 。当在结构体或枚举上派生时, `cmp` 和以 `PartialOrd` 派生实现的 `partial_cmp` 表现一致。
`Ord` trait 也让你明白在一个带注解类型上的任意两个值存在有效顺序。`Ord` trait 实现了 `cmp` 方法,它返回一个 `Ordering` 而不是 `Option<Ordering>`,因为总存在一个合法的顺序。只可以在实现了 `PartialOrd``Eq``Eq` 依赖 `PartialEq`)的类型上使用 `Ord` trait。当在结构体或枚举上派生时 `cmp` 和以 `PartialOrd` 派生实现的 `partial_cmp` 表现一致。
例如,当在 `BTreeSet<T>`(一种基于有序值存储数据的数据结构)上存值时,`Ord` 是必须的。
@ -82,11 +82,11 @@
### 默认值的 `Default`
`Default` trait 使你创建一个类型的默认值。 派生 `Default` 实现了 `default` 函数。`default` 函数的派生实现调用了类型每部分的 `default` 函数,这意味着类型中所有的字段或值也必须实现了 `Default`,这样才能够派生 `Default`
`Default` trait 使你创建一个类型的默认值。派生 `Default` 实现了 `default` 函数。`default` 函数的派生实现调用了类型每部分的 `default` 函数,这意味着类型中所有的字段或值也必须实现了 `Default`,这样才能够派生 `Default`
`Default::default` 函数通常结合结构体更新语法一起使用,这在第五章的 [“使用结构体更新语法从其他实例中创建实例”][creating-instances-from-other-instances-with-struct-update-syntax] 部分有讨论。可以自定义一个结构体的一小部分字段而剩余字段则使用 `..Default::default()` 设置为默认值。
例如,当你在 `Option<T>` 实例上使用 `unwrap_or_default` 方法时,`Default` trait 是必须的。如果 `Option<T>``None`的话, `unwrap_or_default` 方法将返回存储在 `Option<T>``T` 类型的 `Default::default` 的结果。
例如,当你在 `Option<T>` 实例上使用 `unwrap_or_default` 方法时,`Default` trait 是必须的。如果 `Option<T>``None`的话`unwrap_or_default` 方法将返回存储在 `Option<T>``T` 类型的 `Default::default` 的结果。
[creating-instances-from-other-instances-with-struct-update-syntax]: ch05-01-defining-structs.html#使用结构体更新语法从其他实例创建实例
[stack-only-data-copy]: ch04-01-what-is-ownership.html#只在栈上的数据拷贝

View File

@ -30,7 +30,7 @@ $ cargo fmt
如果你编写过 Rust 代码,那么你可能见过那些有很明显修复方式的编译器警告。例如,考虑如下代码:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
fn do_something() {}
@ -69,7 +69,7 @@ $ cargo fix
如果再次查看 _src/main.rs_,会发现 `cargo fix` 修改了代码:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
fn do_something() {}
@ -103,7 +103,7 @@ $ cargo clippy
例如,如果程序使用了如 pi 这样数学常数的近似值,如下:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
fn main() {
@ -128,7 +128,7 @@ error: approximate value of `f{32, 64}::consts::PI` found. Consider using it dir
这告诉我们 Rust 定义了更为精确的常量,而如果使用了这些常量程序将更加准确。如下代码就不会导致 `clippy` 产生任何错误或警告:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
fn main() {

View File

@ -16,7 +16,7 @@ Rust 语言和编译器有一个为期 6 周的发布循环。这意味着用户
- 对于非用户,它表明发布了一些重大进展,这意味着 Rust 可能变得值得一试。
- 对于 Rust 自身开发者,其提供了项目整体的集合点。
在本文档编写时Rust 有三个可用版本Rust 2015、 Rust 2018 和 Rust 2021。本书基于 Rust 2021 edition 风格编写。
在本文档编写时Rust 有三个可用版本Rust 2015、Rust 2018 和 Rust 2021。本书基于 Rust 2021 edition 风格编写。
*Cargo.toml* 中的 `edition` 字段表明代码应该使用哪个版本编译。如果该字段不存在,其默认为 `2015` 以提供后向兼容性。

View File

@ -94,7 +94,7 @@ Rustup 使得改变不同发布通道的 Rust 更为简单,其在全局或分
$ rustup toolchain install nightly
```
你会发现 `rustup` 也安装了所有的 **工具链**_toolchains_ Rust 和其相关组件)。如下是一位作者的 Windows 计算机上的例子:
你会发现 `rustup` 也安装了所有的 **工具链**_toolchains_Rust 和其相关组件)。如下是一位作者的 Windows 计算机上的例子:
```powershell
> rustup toolchain list

View File

@ -97,7 +97,7 @@ $ echo $PATH
$ rustup update
```
若要卸载 Rust 和 `rustup`,请在命令行中运行如下卸载脚本:
若要卸载 Rust 和 `rustup`,请在命令行中运行如下卸载脚本
```console
$ rustup self uninstall

View File

@ -38,7 +38,7 @@ $ cd hello_world
现在打开刚创建的 *main.rs* 文件,输入示例 1-1 中的代码。
<span class="filename">文件名: main.rs</span>
<span class="filename">文件名main.rs</span>
```rust
fn main() {

View File

@ -31,11 +31,11 @@ $ cd hello_cargo
这也会在 *hello_cargo* 目录初始化了一个 git 仓库,以及一个 *.gitignore* 文件。如果在一个已经存在的 git 仓库中运行 `cargo new`,则这些 git 相关文件则不会生成;可以通过运行 `cargo new --vcs=git` 来覆盖这些行为。
> 注意Git 是一个常用的版本控制系统version control system VCS。可以通过 `--vcs` 参数使 `cargo new` 切换到其它版本控制系统VCS或者不使用 VCS。运行 `cargo new --help` 参看可用的选项。
> 注意Git 是一个常用的版本控制系统version control systemVCS。可以通过 `--vcs` 参数使 `cargo new` 切换到其它版本控制系统VCS或者不使用 VCS。运行 `cargo new --help` 参看可用的选项。
请自行选用文本编辑器打开 *Cargo.toml* 文件。它应该看起来如示例 1-2 所示:
<span class="filename">文件名: Cargo.toml</span>
<span class="filename">文件名Cargo.toml</span>
```toml
[package]
@ -60,7 +60,7 @@ edition = "2021"
现在打开 *src/main.rs* 看看:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
fn main() {
@ -140,7 +140,7 @@ $ cargo check
### 把 Cargo 当作习惯
对于简单项目, Cargo 并不比 `rustc` 提供了更多的优势,不过随着开发的深入,终将证明其价值。一旦程序壮大到由多个文件组成,亦或者是需要其他的依赖,让 Cargo 协调构建过程就会简单得多。
对于简单项目Cargo 并不比 `rustc` 提供了更多的优势,不过随着开发的深入,终将证明其价值。一旦程序壮大到由多个文件组成,亦或者是需要其他的依赖,让 Cargo 协调构建过程就会简单得多。
即便 `hello_cargo` 项目十分简单,它现在也使用了很多在你之后的 Rust 生涯将会用到的实用工具。其实,要在任何已存在的项目上工作时,可以使用如下命令通过 Git 检出代码,移动到该项目目录并构建:

View File

@ -21,7 +21,7 @@ $ cd guessing_game
看看生成的 _Cargo.toml_ 文件:
<span class="filename">文件名: Cargo.toml</span>
<span class="filename">文件名Cargo.toml</span>
```toml
{{#include ../listings/ch02-guessing-game-tutorial/no-listing-01-cargo-new/Cargo.toml}}
@ -29,7 +29,7 @@ $ cd guessing_game
正如第一章那样,`cargo new` 生成了一个 “Hello, world!” 程序。查看 _src/main.rs_ 文件:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch02-guessing-game-tutorial/no-listing-01-cargo-new/src/main.rs}}
@ -49,7 +49,7 @@ $ cd guessing_game
猜猜看程序的第一部分请求和处理用户输入,并检查输入是否符合预期的格式。首先,允许玩家输入猜测。在 _src/main.rs_ 中输入示例 2-1 中的代码。
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch02-guessing-game-tutorial/listing-02-01/src/main.rs:all}}
@ -106,7 +106,7 @@ let mut bananas = 5; // 可变
> 注意:`//` 语法开始一个注释持续到行尾。Rust 忽略注释中的所有内容,[第三章][comments]将会详细介绍注释。
回到猜猜看程序中。现在我们知道了 `let mut guess` 会引入一个叫做 `guess` 的可变变量。等号(`=`告诉Rust我们现在想将某个值绑定在变量上。等号的右边是 `guess` 所绑定的值,它是 `String::new` 的结果,这个函数会返回一个 `String` 的新实例。[`String`][string]<!-- ignore --> 是一个标准库提供的字符串类型,它是 UTF-8 编码的可增长文本块。
回到猜猜看程序中。现在我们知道了 `let mut guess` 会引入一个叫做 `guess` 的可变变量。等号(`=`)告诉 Rust 我们现在想将某个值绑定在变量上。等号的右边是 `guess` 所绑定的值,它是 `String::new` 的结果,这个函数会返回一个 `String` 的新实例。[`String`][string]<!-- ignore --> 是一个标准库提供的字符串类型,它是 UTF-8 编码的可增长文本块。
`::new` 那一行的 `::` 语法表明 `new``String` 类型的一个 **关联函数**_associated function_。关联函数是针对类型实现的在这个例子中是 `String`,而不是 `String` 的某个特定实例。一些语言中把它称为 **静态方法**_static method_
@ -209,7 +209,7 @@ You guessed: 6
Cargo 对外部 crate 的运用是其真正的亮点所在。在我们使用 `rand` 编写代码之前,需要修改 *Cargo.toml* 文件,引入一个 `rand` 依赖。现在打开这个文件并将下面这一行添加到 `[dependencies]` 片段标题之下。在当前版本下,请确保按照我们这里的方式指定 `rand`,否则本教程中的示例代码可能无法工作。
<span class="filename">文件名: Cargo.toml</span>
<span class="filename">文件名Cargo.toml</span>
```toml
{{#include ../listings/ch02-guessing-game-tutorial/listing-02-02/Cargo.toml:8:}}
@ -248,7 +248,7 @@ $ cargo build
现在我们有了一个外部依赖Cargo 从 _registry_ 上获取所有包的最新版本信息,这是一份来自 [Crates.io][cratesio] 的数据拷贝。Crates.io 是 Rust 生态环境中的开发者们向他人贡献 Rust 开源项目的地方。
在更新完 registry 后Cargo 检查 `[dependencies]` 片段并下载列表中包含但还未下载的 crates 。本例中,虽然只声明了 `rand` 一个依赖,然而 Cargo 还是额外获取了 `rand` 所需要的其他 crates因为 `rand` 依赖它们来正常工作。下载完成后Rust 编译依赖,然后使用这些依赖编译项目。
在更新完 registry 后Cargo 检查 `[dependencies]` 片段并下载列表中包含但还未下载的 crates。本例中虽然只声明了 `rand` 一个依赖,然而 Cargo 还是额外获取了 `rand` 所需要的其他 crates因为 `rand` 依赖它们来正常工作。下载完成后Rust 编译依赖,然后使用这些依赖编译项目。
如果不做任何修改,立刻再次运行 `cargo build`,则不会看到任何除了 `Finished` 行之外的输出。Cargo 知道它已经下载并编译了依赖,同时 _Cargo.toml_ 文件也没有变动。Cargo 还知道代码也没有任何修改,所以它不会重新编译代码。因为无事可做,它简单的退出了。
@ -264,7 +264,7 @@ $ cargo build
#### _Cargo.lock_ 文件确保构建是可重现的
Cargo 有一个机制来确保任何人在任何时候重新构建代码都会产生相同的结果Cargo 只会使用你指定的依赖版本,除非你又手动指定了别的。例如,如果下周 `rand` crate 的 `0.8.6` 版本出来了,它修复了一个重要的 bug同时也含有一个会破坏代码运行的缺陷。为了处理这个问题Rust在你第一次运行 `cargo build` 时建立了 *Cargo.lock* 文件,我们现在可以在*guessing_game* 目录找到它。
Cargo 有一个机制来确保任何人在任何时候重新构建代码都会产生相同的结果Cargo 只会使用你指定的依赖版本,除非你又手动指定了别的。例如,如果下周 `rand` crate 的 `0.8.6` 版本出来了,它修复了一个重要的 bug同时也含有一个会破坏代码运行的缺陷。为了处理这个问题Rust 在你第一次运行 `cargo build` 时建立了 *Cargo.lock* 文件,我们现在可以在*guessing_game* 目录找到它。
当第一次构建项目时Cargo 计算出所有符合要求的依赖版本并写入 *Cargo.lock* 文件。当将来构建项目时Cargo 会发现 *Cargo.lock* 已存在并使用其中指定的版本,而不是再次计算所有的版本。这使得你拥有了一个自动化的可重现的构建。换句话说,项目会持续使用 `0.8.5` 直到你显式升级,多亏有了 *Cargo.lock* 文件。由于 *Cargo.lock* 文件对于“可重复构建”非常重要,因此它通常会和项目中的其余代码一样纳入到版本控制系统中。
@ -294,7 +294,7 @@ rand = "0.9.0"
让我们开始使用 `rand` 来生成一个猜猜看随机数。下一步是更新 *src/main.rs*,如示例 2-3 所示。
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch02-guessing-game-tutorial/listing-02-03/src/main.rs:all}}
@ -306,7 +306,7 @@ rand = "0.9.0"
接下来,我们在中间还新增加了两行。第一行调用了 `rand::thread_rng` 函数提供实际使用的随机数生成器:它位于当前执行线程的本地环境中,并从操作系统获取 seed。接着调用随机数生成器的 `gen_range` 方法。这个方法由 `use rand::Rng` 语句引入到作用域的 `Rng` trait 定义。`gen_range` 方法获取一个范围表达式range expression作为参数并生成一个在此范围之间的随机数。这里使用的这类范围表达式使用了 `start..=end` 这样的形式,也就是说包含了上下端点,所以需要指定 `1..=100` 来请求一个 1 和 100 之间的数。
> 注意:你不可能凭空就知道应该 use 哪个 trait 以及该从 crate 中调用哪个方法因此每个crate 有使用说明文档。Cargo 有一个很棒的功能是:运行 `cargo doc --open` 命令来构建所有本地依赖提供的文档,并在浏览器中打开。例如,假设你对 `rand` crate 中的其他功能感兴趣,你可以运行 `cargo doc --open` 并点击左侧导航栏中的 `rand`
> 注意:你不可能凭空就知道应该 use 哪个 trait 以及该从 crate 中调用哪个方法,因此每个 crate 有使用说明文档。Cargo 有一个很棒的功能是:运行 `cargo doc --open` 命令来构建所有本地依赖提供的文档,并在浏览器中打开。例如,假设你对 `rand` crate 中的其他功能感兴趣,你可以运行 `cargo doc --open` 并点击左侧导航栏中的 `rand`
新增加的第二行代码打印出了秘密数字。这在开发程序时很有用,因为可以测试它,不过在最终版本中会删掉它。如果游戏一开始就打印出结果就没什么可玩的了!
@ -339,7 +339,7 @@ You guessed: 5
现在有了用户输入和一个随机数,我们可以比较它们。这个步骤如示例 2-4 所示。注意这段代码还不能通过编译,我们稍后会解释。
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch02-guessing-game-tutorial/listing-02-04/src/main.rs:here}}
@ -349,7 +349,7 @@ You guessed: 5
首先我们增加了另一个 `use` 声明,从标准库引入了一个叫做 `std::cmp::Ordering` 的类型到作用域中。 `Ordering` 也是一个枚举,不过它的成员是 `Less`、`Greater` 和 `Equal`。这是比较两个值时可能出现的三种结果。
接着,底部的五行新代码使用了 `Ordering` 类型,`cmp` 方法用来比较两个值并可以在任何可比较的值上调用。它获取一个被比较值的引用:这里是把 `guess``secret_number` 做比较。 然后它会返回一个刚才通过 `use` 引入作用域的 `Ordering` 枚举的成员。使用一个 [`match`][match]<!-- ignore --> 表达式,根据对 `guess``secret_number` 调用 `cmp` 返回的 `Ordering` 成员来决定接下来做什么。
接着,底部的五行新代码使用了 `Ordering` 类型,`cmp` 方法用来比较两个值并可以在任何可比较的值上调用。它获取一个被比较值的引用:这里是把 `guess``secret_number` 做比较。然后它会返回一个刚才通过 `use` 引入作用域的 `Ordering` 枚举的成员。使用一个 [`match`][match]<!-- ignore --> 表达式,根据对 `guess``secret_number` 调用 `cmp` 返回的 `Ordering` 成员来决定接下来做什么。
一个 `match` 表达式由 **分支arms** 构成。一个分支包含一个 **模式***pattern*和表达式开头的值与分支模式相匹配时应该执行的代码。Rust 获取提供给 `match` 的值并挨个检查每个分支的模式。`match` 结构和模式是 Rust 中强大的功能,它体现了代码可能遇到的多种情形,并帮助你确保没有遗漏处理。这些功能将分别在第六章和第十八章详细介绍。
@ -367,7 +367,7 @@ You guessed: 5
所以我们必须把从输入中读取到的 `String` 转换为一个真正的数字类型,才好与秘密数字进行比较。这可以通过在 `main` 函数体中增加如下代码来实现:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch02-guessing-game-tutorial/no-listing-03-convert-string-to-number/src/main.rs:here}}
@ -412,7 +412,7 @@ Too big!
`loop` 关键字创建了一个无限循环。我们会增加循环来给用户更多机会猜数字:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch02-guessing-game-tutorial/no-listing-04-looping/src/main.rs:here}}
@ -453,7 +453,7 @@ note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
让我们增加一个 `break` 语句,在用户猜对时退出游戏:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch02-guessing-game-tutorial/no-listing-05-quitting/src/main.rs:here}}
@ -465,7 +465,7 @@ note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
为了进一步改善游戏性,不要在用户输入非数字时崩溃,需要忽略非数字,让用户可以继续猜测。可以通过修改 `guess``String` 转化为 `u32` 那部分代码来实现,如示例 2-5 所示:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch02-guessing-game-tutorial/listing-02-05/src/main.rs:here}}
@ -506,7 +506,7 @@ You win!
太棒了!再有最后一个小的修改,就能完成猜猜看游戏了:还记得程序依然会打印出秘密数字。在测试时还好,但正式发布时会毁了游戏。删掉打印秘密数字的 `println!`。示例 2-6 为最终代码:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch02-guessing-game-tutorial/listing-02-06/src/main.rs}}

View File

@ -10,7 +10,7 @@
接着,在新建的 *variables* 目录,打开 *src/main.rs* 并将代码替换为如下代码这些代码还不能编译我们会首次检查到不可变错误immutability error
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-01-variables-are-immutable/src/main.rs}}
@ -34,7 +34,7 @@ Rust 编译器保证,如果声明一个值不会变,它就真的不会变,
例如,让我们将 *src/main.rs* 修改为如下代码:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-02-adding-mut/src/main.rs}}
@ -50,7 +50,7 @@ Rust 编译器保证,如果声明一个值不会变,它就真的不会变,
### 常量
类似于不可变变量,*常量(constants)* 是绑定到一个名称的不允许改变的值,不过常量与变量还是有一些区别。
类似于不可变变量,*常量 (constants)* 是绑定到一个名称的不允许改变的值,不过常量与变量还是有一些区别。
首先,不允许对常量使用 `mut`。常量不光默认不可变,它总是不可变。声明常量使用 `const` 关键字而不是 `let`,并且 *必须* 注明值的类型。在下一部分,[“数据类型”][data-types] 中会介绍类型和类型注解,现在无需关心这些细节,记住总是标注类型即可。
@ -64,7 +64,7 @@ Rust 编译器保证,如果声明一个值不会变,它就真的不会变,
const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;
```
常量的名称是 `THREE_HOURS_IN_SECONDS`,它的值被设置为 60一分钟内的秒数乘以 60一小时内的分钟数再乘以 3我们在这个程序中要计算的小时数的结果。Rust 对常量的命名约定是在单词之间使用全大写加下划线。编译器能够在编译时计算一组有限的操作这使我们可以选择以更容易理解和验证的方式写出此值而不是将此常量设置为值10,800。有关声明常量时可以使用哪些操作的详细信息请参阅 [Rust Reference 的常量求值部分][const-eval]。
常量的名称是 `THREE_HOURS_IN_SECONDS`,它的值被设置为 60一分钟内的秒数乘以 60一小时内的分钟数再乘以 3我们在这个程序中要计算的小时数的结果。Rust 对常量的命名约定是在单词之间使用全大写加下划线。编译器能够在编译时计算一组有限的操作,这使我们可以选择以更容易理解和验证的方式写出此值,而不是将此常量设置为值 10,800。有关声明常量时可以使用哪些操作的详细信息请参阅 [Rust Reference 的常量求值部分][const-eval]。
在声明它的作用域之中,常量在整个程序生命周期中都有效,此属性使得常量可以作为多处代码使用的全局范围的值,例如一个游戏中所有玩家可以获取的最高分或者光速。
@ -74,7 +74,7 @@ const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;
正如在[第二章][comparing-the-guess-to-the-secret-number]猜数字游戏中所讲我们可以定义一个与之前变量同名的新变量。Rustacean 们称之为第一个变量被第二个 **隐藏Shadowing** 了,这意味着当您使用变量的名称时,编译器将看到第二个变量。实际上,第二个变量“遮蔽”了第一个变量,此时任何使用该变量名的行为中都会视为是在使用第二个变量,直到第二个变量自己也被隐藏或第二个变量的作用域结束。可以用相同变量名称来隐藏一个变量,以及重复使用 `let` 关键字来多次隐藏,如下所示:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-03-shadowing/src/main.rs}}

View File

@ -43,7 +43,7 @@ let guess: u32 = "42".parse().expect("Not a number!");
每一个有符号的变体可以储存包含从 -(2<sup>n - 1</sup>) 到 2<sup>n - 1</sup> - 1 在内的数字,这里 *n* 是变体使用的位数。所以 `i8` 可以储存从 -(2<sup>7</sup>) 到 2<sup>7</sup> - 1 在内的数字,也就是从 -128 到 127。无符号的变体可以储存从 0 到 2<sup>n</sup> - 1 的数字,所以 `u8` 可以储存从 0 到 2<sup>8</sup> - 1 的数字,也就是从 0 到 255。
另外,`isize` 和 `usize` 类型依赖运行程序的计算机架构64 位架构上它们是 64 位的, 32 位架构上它们是 32 位的。
另外,`isize` 和 `usize` 类型依赖运行程序的计算机架构64 位架构上它们是 64 位的32 位架构上它们是 32 位的。
可以使用表格 3-2 中的任何一种形式编写数字字面值。请注意可以是多种数字类型的数字字面值允许使用类型后缀,例如 `57u8` 来指定类型,同时也允许使用 `_` 做为分隔符以方便读数,例如`1_000`,它的值与你指定的 `1000` 相同。
@ -63,7 +63,7 @@ let guess: u32 = "42".parse().expect("Not a number!");
>
> 比方说有一个 `u8` ,它可以存放从零到 `255` 的值。那么当你将其修改为 `256` 时会发生什么呢?这被称为 “整型溢出”“integer overflow” ),这会导致以下两种行为之一的发生。当在 debug 模式编译时Rust 检查这类问题并使程序 *panic*,这个术语被 Rust 用来表明程序因错误而退出。第九章 [“`panic!` 与不可恢复的错误”][unrecoverable-errors-with-panic] 部分会详细介绍 panic。
>
> 使用 `--release` flag 在 release 模式中构建时Rust **不会**检测会导致 panic 的整型溢出。相反发生整型溢出时, Rust 会进行一种被称为二进制补码 wrapping*twos complement wrapping*)的操作。简而言之,比此类型能容纳最大值还大的值会回绕到最小值,值 `256` 变成 `0`,值 `257` 变成 `1`,依此类推。程序不会 panic不过变量可能也不会是你所期望的值。依赖整型溢出 wrapping 的行为被认为是一种错误。
> 使用 `--release` flag 在 release 模式中构建时Rust **不会**检测会导致 panic 的整型溢出。相反发生整型溢出时Rust 会进行一种被称为二进制补码 wrapping*twos complement wrapping*)的操作。简而言之,比此类型能容纳最大值还大的值会回绕到最小值,值 `256` 变成 `0`,值 `257` 变成 `1`,依此类推。程序不会 panic不过变量可能也不会是你所期望的值。依赖整型溢出 wrapping 的行为被认为是一种错误。
>
> 为了显式地处理溢出的可能性,可以使用这几类标准库提供的原始数字类型方法:
> * 所有模式下都可以使用 `wrapping_*` 方法进行 wrapping如 `wrapping_add`
@ -77,7 +77,7 @@ Rust 也有两个原生的 **浮点数***floating-point numbers*)类型,
这是一个展示浮点数的实例:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-06-floating-point/src/main.rs}}
@ -89,7 +89,7 @@ Rust 也有两个原生的 **浮点数***floating-point numbers*)类型,
Rust 中的所有数字类型都支持基本数学运算:加法、减法、乘法、除法和取余。整数除法会向下舍入到最接近的整数。下面的代码展示了如何在 `let` 语句中使用它们:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-07-numeric-operations/src/main.rs}}
@ -101,7 +101,7 @@ Rust 中的所有数字类型都支持基本数学运算:加法、减法、乘
正如其他大部分编程语言一样Rust 中的布尔类型有两个可能的值:`true` 和 `false`。Rust 中的布尔类型使用 `bool` 表示。例如:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-08-boolean/src/main.rs}}
@ -111,15 +111,15 @@ Rust 中的所有数字类型都支持基本数学运算:加法、减法、乘
#### 字符类型
Rust的 `char` 类型是语言中最原生的字母类型。下面是一些声明 `char` 值的例子:
Rust `char` 类型是语言中最原生的字母类型。下面是一些声明 `char` 值的例子:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-09-char/src/main.rs}}
```
注意,我们用单引号声明 `char` 字面量而与之相反的是使用双引号声明字符串字面量。Rust 的 `char` 类型的大小为四个字节(four bytes),并代表了一个 Unicode 标量值Unicode Scalar Value这意味着它可以比 ASCII 表示更多内容。在 Rust 中带变音符号的字母Accented letters中文、日文、韩文等字符emoji绘文字以及零长度的空白字符都是有效的 `char` 值。Unicode 标量值包含从 `U+0000``U+D7FF``U+E000``U+10FFFF` 在内的值。不过,“字符” 并不是一个 Unicode 中的概念,所以人直觉上的 “字符” 可能与 Rust 中的 `char` 并不符合。第八章的 [“使用字符串存储 UTF-8 编码的文本”][strings] 中将详细讨论这个主题。
注意,我们用单引号声明 `char` 字面量而与之相反的是使用双引号声明字符串字面量。Rust 的 `char` 类型的大小为四个字节 (four bytes),并代表了一个 Unicode 标量值Unicode Scalar Value这意味着它可以比 ASCII 表示更多内容。在 Rust 中带变音符号的字母Accented letters中文、日文、韩文等字符emoji绘文字以及零长度的空白字符都是有效的 `char` 值。Unicode 标量值包含从 `U+0000``U+D7FF``U+E000``U+10FFFF` 在内的值。不过,“字符” 并不是一个 Unicode 中的概念,所以人直觉上的 “字符” 可能与 Rust 中的 `char` 并不符合。第八章的 [“使用字符串存储 UTF-8 编码的文本”][strings] 中将详细讨论这个主题。
### 复合类型
@ -131,7 +131,7 @@ Rust的 `char` 类型是语言中最原生的字母类型。下面是一些声
我们使用包含在圆括号中的逗号分隔的值列表来创建一个元组。元组中的每一个位置都有一个类型,而且这些不同值的类型也不必是相同的。这个例子中使用了可选的类型注解:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-10-tuples/src/main.rs}}
@ -139,7 +139,7 @@ Rust的 `char` 类型是语言中最原生的字母类型。下面是一些声
`tup` 变量绑定到整个元组上因为元组是一个单独的复合元素。为了从元组中获取单个值可以使用模式匹配pattern matching来解构destructure元组值像这样
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-11-destructuring-tuples/src/main.rs}}
@ -149,7 +149,7 @@ Rust的 `char` 类型是语言中最原生的字母类型。下面是一些声
我们也可以使用点号(`.`)后跟值的索引来直接访问它们。例如:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-12-tuple-indexing/src/main.rs}}
@ -161,11 +161,11 @@ Rust的 `char` 类型是语言中最原生的字母类型。下面是一些声
#### 数组类型
另一个包含多个值的方式是 **数组***array*。与元组不同数组中的每个元素的类型必须相同。Rust 中的数组与一些其他语言中的数组不同Rust中的数组长度是固定的。
另一个包含多个值的方式是 **数组***array*。与元组不同数组中的每个元素的类型必须相同。Rust 中的数组与一些其他语言中的数组不同Rust 中的数组长度是固定的。
我们将数组的值写成在方括号内,用逗号分隔:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-13-arrays/src/main.rs}}
@ -173,7 +173,7 @@ Rust的 `char` 类型是语言中最原生的字母类型。下面是一些声
当你想要在栈stack而不是在堆heap上为数据分配空间[第四章][stack-and-heap]将讨论栈与堆的更多内容),或者是想要确保总是有固定数量的元素时,数组非常有用。但是数组并不如 vector 类型灵活。vector 类型是标准库提供的一个 **允许** 增长和缩小长度的类似数组的集合类型。当不确定是应该使用数组还是 vector 的时候,那么很可能应该使用 vector。[第八章][vectors]会详细讨论 vector。
然而,当你确定元素个数不会改变时,数组会更有用。例如,当你在一个程序中使用月份名字时,你更应趋向于使用数组而不是 vector因为你确定只会有12个元素。
然而,当你确定元素个数不会改变时,数组会更有用。例如,当你在一个程序中使用月份名字时,你更应趋向于使用数组而不是 vector因为你确定只会有 12 个元素。
```rust
let months = ["January", "February", "March", "April", "May", "June", "July",
@ -198,9 +198,9 @@ let a = [3; 5];
##### 访问数组元素
数组是可以在栈(stack)上分配的已知固定大小的单个内存块。可以使用索引来访问数组的元素,像这样:
数组是可以在栈 (stack) 上分配的已知固定大小的单个内存块。可以使用索引来访问数组的元素,像这样:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-14-array-indexing/src/main.rs}}
@ -212,7 +212,7 @@ let a = [3; 5];
让我们看看如果我们访问数组结尾之后的元素会发生什么呢?比如你执行以下代码,它使用类似于第 2 章中的猜数字游戏的代码从用户那里获取数组索引:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore,panics
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-15-invalid-array-access/src/main.rs}}

View File

@ -8,13 +8,13 @@
Rust 代码中的函数和变量名使用 *snake case* 规范风格。在 snake case 中,所有字母都是小写并使用下划线分隔单词。这是一个包含函数定义示例的程序:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-16-functions/src/main.rs}}
```
我们在Rust 中通过输入 `fn` 后面跟着函数名和一对圆括号来定义函数。大括号告诉编译器哪里是函数体的开始和结尾。
我们在 Rust 中通过输入 `fn` 后面跟着函数名和一对圆括号来定义函数。大括号告诉编译器哪里是函数体的开始和结尾。
可以使用函数名后跟圆括号来调用我们定义过的任意函数。因为程序中已定义 `another_function` 函数,所以可以在 `main` 函数中调用它。注意,源码中 `another_function` 定义在 `main` 函数 **之后**也可以定义在之前。Rust 不关心函数定义所在的位置,只要函数被调用时出现在调用之处可见的作用域内就行。
@ -32,7 +32,7 @@ Rust 代码中的函数和变量名使用 *snake case* 规范风格。在 snake
在这版 `another_function` 中,我们增加了一个参数:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-17-functions-with-parameters/src/main.rs}}
@ -50,13 +50,13 @@ Rust 代码中的函数和变量名使用 *snake case* 规范风格。在 snake
当定义多个参数时,使用逗号分隔,像这样:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-18-functions-with-multiple-parameters/src/main.rs}}
```
这个例子创建了一个名为 `print_labeled_measurement` 的函数,它有两个参数。第一个参数名为 `value` 类型是 `i32`。第二个参数是 `unit_label` ,类型是 `char`。然后,该函数打印包含 `value``unit_label` 的文本。
这个例子创建了一个名为 `print_labeled_measurement` 的函数,它有两个参数。第一个参数名为 `value`,类型是 `i32`。第二个参数是 `unit_label` ,类型是 `char`。然后,该函数打印包含 `value``unit_label` 的文本。
尝试运行代码。使用上面的例子替换当前 *functions* 项目的 *src/main.rs* 文件,并用 `cargo run` 运行它:
@ -75,7 +75,7 @@ Rust 代码中的函数和变量名使用 *snake case* 规范风格。在 snake
实际上,我们已经使用过语句和表达式。使用 `let` 关键字创建变量并绑定一个值是一个语句。在列表 3-1 中,`let y = 6;` 是一个语句。
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch03-common-programming-concepts/listing-03-01/src/main.rs}}
@ -87,7 +87,7 @@ Rust 代码中的函数和变量名使用 *snake case* 规范风格。在 snake
语句不返回值。因此,不能把 `let` 语句赋值给另一个变量,比如下面的例子尝试做的,会产生一个错误:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-19-statements-vs-expressions/src/main.rs}}
@ -103,7 +103,7 @@ Rust 代码中的函数和变量名使用 *snake case* 规范风格。在 snake
表达式会计算出一个值,并且你将编写的大部分 Rust 代码是由表达式组成的。考虑一个数学运算,比如 `5 + 6`,这是一个表达式并计算出值 `11`。表达式可以是语句的一部分:在示例 3-1 中,语句 `let y = 6;` 中的 `6` 是一个表达式,它计算出的值是 `6`。函数调用是一个表达式。宏调用是一个表达式。用大括号创建的一个新的块作用域也是一个表达式,例如:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-20-blocks-are-expressions/src/main.rs}}
@ -124,7 +124,7 @@ Rust 代码中的函数和变量名使用 *snake case* 规范风格。在 snake
函数可以向调用它的代码返回值。我们并不对返回值命名,但要在箭头(`->`)后声明它的类型。在 Rust 中,函数的返回值等同于函数体最后一个表达式的值。使用 `return` 关键字和指定值,可从函数中提前返回;但大部分函数隐式的返回最后的表达式。这是一个有返回值的函数的例子:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-21-function-return-values/src/main.rs}}
@ -146,7 +146,7 @@ let x = 5;
让我们看看另一个例子:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-22-function-parameter-and-return/src/main.rs}}
@ -154,7 +154,7 @@ let x = 5;
运行代码会打印出 `The value of x is: 6`。但如果在包含 `x + 1` 的行尾加上一个分号,把它从表达式变成语句,我们将看到一个错误。
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-23-statements-dont-return-values/src/main.rs}}

View File

@ -22,7 +22,7 @@
注释也可以放在包含代码的行的末尾:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-24-comments-end-of-line/src/main.rs}}
@ -30,7 +30,7 @@
不过你更经常看到的是以这种格式使用它们,也就是位于它所解释的代码行的上面一行:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-25-comments-above-line/src/main.rs}}

View File

@ -12,7 +12,7 @@
*projects* 目录新建一个叫做 *branches* 的项目,来学习 `if` 表达式。在 *src/main.rs* 文件中,输入如下内容:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-26-if-true/src/main.rs}}
@ -42,7 +42,7 @@
另外值得注意的是代码中的条件 **必须**`bool` 值。如果条件不是 `bool` 值,我们将得到一个错误。例如,尝试运行以下代码:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-28-if-condition-must-be-bool/src/main.rs}}
@ -56,7 +56,7 @@
这个错误表明 Rust 期望一个 `bool` 却得到了一个整数。不像 Ruby 或 JavaScript 这样的语言Rust 并不会尝试自动地将非布尔值转换为布尔值。必须总是显式地使用布尔值作为 `if` 的条件。例如,如果想要 `if` 代码块只在一个数字不等于 `0` 时执行,可以把 `if` 表达式修改成下面这样:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-29-if-not-equal-0/src/main.rs}}
@ -68,7 +68,7 @@
可以将 `else if` 表达式与 `if``else` 组合来实现多重条件。例如:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-30-else-if/src/main.rs}}
@ -88,7 +88,7 @@
因为 `if` 是一个表达式,我们可以在 `let` 语句的右侧使用它,例如在示例 3-2 中:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch03-common-programming-concepts/listing-03-02/src/main.rs}}
@ -104,7 +104,7 @@
记住,代码块的值是其最后一个表达式的值,而数字本身就是一个表达式。在这个例子中,整个 `if` 表达式的值取决于哪个代码块被执行。这意味着 `if` 的每个分支的可能的返回值都必须是相同类型;在示例 3-2 中,`if` 分支和 `else` 分支的结果都是 `i32` 整型。如果它们的类型不匹配,如下面这个例子,则会出现一个错误:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-31-arms-must-return-same-type/src/main.rs}}
@ -130,7 +130,7 @@ Rust 有三种循环:`loop`、`while` 和 `for`。我们每一个都试试。
作为一个例子,将 *loops* 目录中的 *src/main.rs* 文件修改为如下:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-32-loop/src/main.rs}}
@ -174,7 +174,7 @@ again!
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-32-5-loop-labels/src/main.rs}}
```
外层循环有一个标签 `counting_up`,它将从 0 数到 2。没有标签的内部循环从 10 向下数到 9。第一个没有指定标签的 `break` 将只退出内层循环。`break 'counting_up;` 语句将退出外层循环。这个代码打印:
外层循环有一个标签 `counting_up`,它将从 0 数到 2。没有标签的内部循环从 10 向下数到 9。第一个没有指定标签的 `break` 将只退出内层循环。`break 'counting_up;` 语句将退出外层循环。这个代码打印
```console
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-32-5-loop-labels/output.txt}}
@ -186,7 +186,7 @@ again!
然而这个模式太常用了Rust 为此内置了一个语言结构,它被称为 `while` 循环。示例 3-3 使用了 `while`:程序循环三次,每次数字都减一。接着,在循环结束后,打印出另一个信息并退出。
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch03-common-programming-concepts/listing-03-03/src/main.rs}}
@ -200,7 +200,7 @@ again!
可以使用 `while` 结构来遍历集合中的元素,比如数组。例如,看看示例 3-4。
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch03-common-programming-concepts/listing-03-04/src/main.rs}}
@ -220,7 +220,7 @@ again!
作为更简洁的替代方案,可以使用 `for` 循环来对一个集合的每个元素执行一些代码。`for` 循环看起来如示例 3-5 所示:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch03-common-programming-concepts/listing-03-05/src/main.rs}}
@ -236,7 +236,7 @@ again!
下面是一个使用 `for` 循环来倒计时的例子,它还使用了一个我们还未讲到的方法,`rev`,用来反转 range
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-34-for-range/src/main.rs}}

View File

@ -74,7 +74,7 @@ let s = String::from("hello");
这两个冒号 `::` 是运算符,允许将特定的 `from` 函数置于 `String` 类型的命名空间namespace而不需要使用类似 `string_from` 这样的名字。在第五章的 [“方法语法”“Method Syntax”][method-syntax] 部分会着重讲解这个语法而且在第七章的 [“路径用于引用模块树中的项”][paths-module-tree] 中会讲到模块的命名空间。
**可以** 修改此类字符串
**可以** 修改此类字符串:
```rust
{{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-01-can-mutate-string/src/main.rs:here}}
@ -93,7 +93,7 @@ let s = String::from("hello");
第一部分由我们完成:当调用 `String::from` 时,它的实现 (*implementation*) 请求其所需的内存。这在编程语言中是非常通用的。
然而,第二部分实现起来就各有区别了。在有 **垃圾回收***garbage collector**GC*)的语言中, GC 记录并清除不再使用的内存,而我们并不需要关心它。在大部分没有 GC 的语言中,识别出不再使用的内存并调用代码显式释放就是我们的责任了,跟请求内存的时候一样。从历史的角度上说正确处理内存回收曾经是一个困难的编程问题。如果忘记回收了会浪费内存。如果过早回收了,将会出现无效变量。如果重复回收,这也是个 bug。我们需要精确的为一个 `allocate` 配对一个 `free`
然而,第二部分实现起来就各有区别了。在有 **垃圾回收***garbage collector**GC*的语言中GC 记录并清除不再使用的内存,而我们并不需要关心它。在大部分没有 GC 的语言中,识别出不再使用的内存并调用代码显式释放就是我们的责任了,跟请求内存的时候一样。从历史的角度上说正确处理内存回收曾经是一个困难的编程问题。如果忘记回收了会浪费内存。如果过早回收了,将会出现无效变量。如果重复回收,这也是个 bug。我们需要精确的为一个 `allocate` 配对一个 `free`
Rust 采取了一个不同的策略:内存在拥有它的变量离开作用域后就被自动释放。下面是示例 4-1 中作用域例子的一个使用 `String` 而不是字符串字面值的版本:
@ -111,7 +111,7 @@ Rust 采取了一个不同的策略:内存在拥有它的变量离开作用域
#### 变量与数据交互的方式(一):移动
在Rust 中,多个变量可以采取不同的方式与同一数据进行交互。让我们看看示例 4-2 中一个使用整型的例子。
Rust 中,多个变量可以采取不同的方式与同一数据进行交互。让我们看看示例 4-2 中一个使用整型的例子。
```rust
{{#rustdoc_include ../listings/ch04-understanding-ownership/listing-04-02/src/main.rs:here}}
@ -227,7 +227,7 @@ Rust 不允许自身或其任何部分实现了 `Drop` trait 的类型使用 `Co
将值传递给函数与给变量赋值的原理相似。向函数传递值可能会移动或者复制,就像赋值语句一样。示例 4-3 使用注释展示变量何时进入和离开作用域:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch04-understanding-ownership/listing-04-03/src/main.rs}}
@ -241,7 +241,7 @@ Rust 不允许自身或其任何部分实现了 `Drop` trait 的类型使用 `Co
返回值也可以转移所有权。示例 4-4 展示了一个返回了某些值的示例,与示例 4-3 一样带有类似的注释。
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch04-understanding-ownership/listing-04-04/src/main.rs}}
@ -255,7 +255,7 @@ Rust 不允许自身或其任何部分实现了 `Drop` trait 的类型使用 `Co
我们可以使用元组来返回多个值,如示例 4-5 所示。
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch04-understanding-ownership/listing-04-05/src/main.rs}}

View File

@ -9,7 +9,7 @@
下面是如何定义并使用一个(新的)`calculate_length` 函数,它以一个对象的引用作为参数而不是获取值的所有权:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-07-reference/src/main.rs:all}}
@ -45,7 +45,7 @@ string data on the heap." src="img/trpl04-05.svg" class="center" />
如果我们尝试修改借用的变量呢?尝试示例 4-6 中的代码。剧透:这行不通!
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch04-understanding-ownership/listing-04-06/src/main.rs}}
@ -65,7 +65,7 @@ string data on the heap." src="img/trpl04-05.svg" class="center" />
我们通过一个小调整就能修复示例 4-6 代码中的错误,允许我们修改一个借用的值,这就是 **可变引用***mutable reference*
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-09-fixes-listing-04-06/src/main.rs}}
@ -75,7 +75,7 @@ string data on the heap." src="img/trpl04-05.svg" class="center" />
可变引用有一个很大的限制:如果你有一个对该变量的可变引用,你就不能再创建对该变量的引用。这些尝试创建两个 `s` 的可变引用的代码会失败:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-10-multiple-mut-not-allowed/src/main.rs:here}}
@ -135,7 +135,7 @@ Rust 在同时使用可变与不可变引用时也采用的类似的规则。这
让我们尝试创建一个悬垂引用Rust 会通过一个编译时错误来避免:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-14-dangling-reference/src/main.rs}}
@ -156,7 +156,7 @@ for it to be borrowed from
让我们仔细看看我们的 `dangle` 代码的每一步到底发生了什么:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-15-dangling-reference-annotated/src/main.rs:here}}

View File

@ -16,7 +16,7 @@ fn first_word(s: &String) -> ?
`first_word` 函数有一个参数 `&String`。因为我们不需要所有权,所以这没有问题。不过应该返回什么呢?我们并没有一个真正获取 **部分** 字符串的办法。不过,我们可以返回单词结尾的索引,结尾由一个空格表示。试试如示例 4-7 中的代码。
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch04-understanding-ownership/listing-04-07/src/main.rs:here}}
@ -48,7 +48,7 @@ fn first_word(s: &String) -> ?
现在有了一个找到字符串中第一个单词结尾索引的方法,不过这有一个问题。我们返回了一个独立的 `usize`,不过它只在 `&String` 的上下文中才是一个有意义的数字。换句话说,因为它是一个与 `String` 相分离的值,无法保证将来它仍然有效。考虑一下示例 4-8 中使用了示例 4-7 中 `first_word` 函数的程序。
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch04-understanding-ownership/listing-04-08/src/main.rs:here}}
@ -123,7 +123,7 @@ let slice = &s[..];
在记住所有这些知识后,让我们重写 `first_word` 来返回一个 slice。“字符串 slice” 的类型声明写作 `&str`
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-18-first-word-slice/src/main.rs:here}}
@ -141,7 +141,7 @@ fn second_word(s: &String) -> &str {
现在我们有了一个不易混淆且直观的 API 了,因为编译器会确保指向 `String` 的引用持续有效。还记得示例 4-8 程序中,那个当我们获取第一个单词结尾的索引后,接着就清除了字符串导致索引就无效的 bug 吗那些代码在逻辑上是不正确的但却没有显示任何直接的错误。问题会在之后尝试对空字符串使用第一个单词的索引时出现。slice 就不可能出现这种 bug 并让我们更早的知道出问题了。使用 slice 版本的 `first_word` 会抛出一个编译时错误:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-19-slice-error/src/main.rs:here}}
@ -185,7 +185,7 @@ fn first_word(s: &String) -> &str {
如果有一个字符串 slice可以直接传递它。如果有一个 `String`,则可以传递整个 `String` 的 slice 或对 `String` 的引用。这种灵活性利用了 *deref coercions* 的优势,这个特性我们将在[“函数和方法的隐式 Deref 强制转换”][deref-coercions]章节中介绍。定义一个获取字符串 slice 而不是 `String` 引用的函数使得我们的 API 更加通用并且不会丢失任何功能:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch04-understanding-ownership/listing-04-09/src/main.rs:usage}}

View File

@ -8,7 +8,7 @@
定义结构体,需要使用 `struct` 关键字并为整个结构体提供一个名字。结构体的名字需要描述它所组合的数据的意义。接着,在大括号中,定义每一部分数据的名字和类型,我们称为 **字段***field*)。例如,示例 5-1 展示了一个存储用户账号信息的结构体:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-01/src/main.rs:here}}
@ -16,9 +16,9 @@
<span class="caption">示例 5-1`User` 结构体定义</span>
一旦定义了结构体后,为了使用它,通过为每个字段指定具体值来创建这个结构体的 **实例**。创建一个实例需要以结构体的名字开头,接着在大括号中使用 `key: value` 键-值对的形式提供字段,其中 key 是字段的名字value 是需要存储在字段中的数据值。实例中字段的顺序不需要和它们在结构体中声明的顺序一致。换句话说,结构体的定义就像一个类型的通用模板,而实例则会在这个模板中放入特定数据来创建这个类型的值。例如,可以像示例 5-2 这样来声明一个特定的用户:
一旦定义了结构体后,为了使用它,通过为每个字段指定具体值来创建这个结构体的 **实例**。创建一个实例需要以结构体的名字开头,接着在大括号中使用 `key: value` - 值对的形式提供字段,其中 key 是字段的名字value 是需要存储在字段中的数据值。实例中字段的顺序不需要和它们在结构体中声明的顺序一致。换句话说,结构体的定义就像一个类型的通用模板,而实例则会在这个模板中放入特定数据来创建这个类型的值。例如,可以像示例 5-2 这样来声明一个特定的用户:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-02/src/main.rs:here}}
@ -28,7 +28,7 @@
为了从结构体中获取某个特定的值,可以使用点号。举个例子,想要用户的邮箱地址,可以用 `user1.email`。如果结构体的实例是可变的,我们可以使用点号并为对应的字段赋值。示例 5-3 展示了如何改变一个可变的 `User` 实例中 `email` 字段的值:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-03/src/main.rs:here}}
@ -40,7 +40,7 @@
示例 5-4 显示了一个 `build_user` 函数,它返回一个带有给定的 email 和用户名的 `User` 结构体实例。`active` 字段的值为 `true`,并且 `sign_in_count` 的值为 `1`
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-04/src/main.rs:here}}
@ -54,7 +54,7 @@
因为示例 5-4 中的参数名与字段名都完全相同,我们可以使用 **字段初始化简写语法***field init shorthand*)来重写 `build_user`,这样其行为与之前完全相同,不过无需重复 `username``email` 了,如示例 5-5 所示。
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-05/src/main.rs:here}}
@ -70,7 +70,7 @@
首先,示例 5-6 展示了不使用更新语法时,如何在 `user2` 中创建一个新 `User` 实例。我们为 `email` 设置了新的值,其他值则使用了实例 5-2 中创建的 `user1` 中的同名值:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-06/src/main.rs:here}}
@ -80,7 +80,7 @@
使用结构体更新语法,我们可以通过更少的代码来达到相同的效果,如示例 5-7 所示。`..` 语法指定了剩余未显式设置值的字段应有与给定实例对应字段相同的值。
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-07/src/main.rs:here}}
@ -98,7 +98,7 @@
要定义元组结构体,以 `struct` 关键字和结构体名开头并后跟元组中的类型。例如,下面是两个分别叫做 `Color``Point` 元组结构体的定义和用法:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/no-listing-01-tuple-structs/src/main.rs}}
@ -110,7 +110,7 @@
我们也可以定义一个没有任何字段的结构体!它们被称为 **类单元结构体***unit-like structs*)因为它们类似于 `()`,即[“元组类型”][tuples]一节中提到的 unit 类型。类单元结构体常常在你想要在某个类型上实现 trait 但不需要在类型中存储数据的时候发挥作用。我们将在第十章介绍 trait。下面是一个声明和实例化一个名为 `AlwaysEqual` 的 unit 结构的例子。
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/no-listing-04-unit-like-structs/src/main.rs}}
@ -124,7 +124,7 @@
>
> 可以使结构体存储被其他对象拥有的数据的引用,不过这么做的话需要用上 **生命周期***lifetimes*),这是一个第十章会讨论的 Rust 功能。生命周期确保结构体引用的数据有效性跟结构体本身保持一致。如果你尝试在结构体中存储一个引用而不指定生命周期将是无效的,比如这样:
>
> <span class="filename">文件名: src/main.rs</span>
> <span class="filename">文件名src/main.rs</span>
>
> ```rust,ignore,does_not_compile
> struct User {

View File

@ -8,7 +8,7 @@
使用 Cargo 新建一个叫做 *rectangles* 的二进制程序,它获取以像素为单位的长方形的宽度和高度,并计算出长方形的面积。示例 5-8 显示了位于项目的 *src/main.rs* 中的小程序,它刚刚好实现此功能:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-08/src/main.rs:all}}
@ -36,7 +36,7 @@
示例 5-9 展示了使用元组的另一个程序版本。
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-09/src/main.rs}}
@ -52,7 +52,7 @@
我们使用结构体为数据命名来为其赋予意义。我们可以将我们正在使用的元组转换成一个有整体名称而且每个部分也有对应名字的结构体,如示例 5-10 所示:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-10/src/main.rs}}
@ -70,7 +70,7 @@
在调试程序时打印出 `Rectangle` 实例来查看其所有字段的值非常有用。示例 5-11 像前面章节那样尝试使用 [`println!` 宏][println]。但这并不行。
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-11/src/main.rs}}
@ -108,7 +108,7 @@
Rust **确实** 包含了打印出调试信息的功能,不过我们必须为结构体显式选择这个功能。为此,在结构体定义之前加上外部属性 `#[derive(Debug)]`,如示例 5-12 所示:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-12/src/main.rs}}
@ -144,7 +144,7 @@ Rust **确实** 包含了打印出调试信息的功能,不过我们必须为
{{#include ../listings/ch05-using-structs-to-structure-related-data/no-listing-05-dbg-macro/output.txt}}
```
我们可以看到第一点输出来自 *src/main.rs* 第 10 行,我们正在调试表达式 `30 * scale`,其结果值是 `60`(为整数实现的 `Debug` 格式化是只打印它们的值)。在 *src/main.rs* 第 14行 的 `dbg!` 调用输出 `&rect1` 的值,即 `Rectangle` 结构。这个输出使用了更为易读的 `Debug` 格式。当你试图弄清楚你的代码在做什么时,`dbg!` 宏可能真的很有帮助!
我们可以看到第一点输出来自 *src/main.rs* 第 10 行,我们正在调试表达式 `30 * scale`,其结果值是 `60`(为整数实现的 `Debug` 格式化是只打印它们的值)。在 *src/main.rs* 第 14 行 的 `dbg!` 调用输出 `&rect1` 的值,即 `Rectangle` 结构。这个输出使用了更为易读的 `Debug` 格式。当你试图弄清楚你的代码在做什么时,`dbg!` 宏可能真的很有帮助
除了 `Debug` traitRust 还为我们提供了很多可以通过 `derive` 属性来使用的 trait他们可以为我们的自定义类型增加实用的行为。[附录 C][app-c] 中列出了这些 trait 和行为。第十章会介绍如何通过自定义行为来实现这些 trait同时还有如何创建你自己的 trait。除了 `derive` 之外,还有很多属性;更多信息请参见 [Rust Reference][attributes] 的 Attributes 部分。

View File

@ -10,7 +10,7 @@
让我们把前面实现的获取一个 `Rectangle` 实例作为参数的 `area` 函数,改写成一个定义于 `Rectangle` 结构体上的 `area` 方法,如示例 5-13 所示:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-13/src/main.rs}}
@ -28,7 +28,7 @@
请注意,我们可以选择将方法的名称与结构中的一个字段相同。例如,我们可以在 `Rectangle` 上定义一个方法,并命名为 `width`
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/no-listing-06-method-field-interaction/src/main.rs:here}}
@ -73,7 +73,7 @@
让我们通过实现 `Rectangle` 结构体上的另一方法来练习使用方法。这回,我们让一个 `Rectangle` 的实例获取另一个 `Rectangle` 实例,如果 `self` (第一个 `Rectangle`)能完全包含第二个长方形则返回 `true`;否则返回 `false`。一旦我们定义了 `can_hold` 方法,就可以编写示例 5-14 中的代码。
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-14/src/main.rs}}
@ -90,7 +90,7 @@ Can rect1 hold rect3? false
因为我们想定义一个方法,所以它应该位于 `impl Rectangle` 块中。方法名是 `can_hold`,并且它会获取另一个 `Rectangle` 的不可变借用作为参数。通过观察调用方法的代码可以看出参数是什么类型的:`rect1.can_hold(&rect2)` 传入了 `&rect2`,它是一个 `Rectangle` 的实例 `rect2` 的不可变借用。这是可以理解的,因为我们只需要读取 `rect2`(而不是写入,这意味着我们需要一个不可变借用),而且希望 `main` 保持 `rect2` 的所有权,这样就可以在调用这个方法后继续使用它。`can_hold` 的返回值是一个布尔值,其实现会分别检查 `self` 的宽高是否都大于另一个 `Rectangle`。让我们在示例 5-13 的 `impl` 块中增加这个新的 `can_hold` 方法,如示例 5-15 所示:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-15/src/main.rs:here}}
@ -106,7 +106,7 @@ Can rect1 hold rect3? false
不是方法的关联函数经常被用作返回一个结构体新实例的构造函数。这些函数的名称通常为 `new` ,但 `new` 并不是一个关键字。例如我们可以提供一个叫做 `square` 关联函数,它接受一个维度参数并且同时作为宽和高,这样可以更轻松的创建一个正方形 `Rectangle` 而不必指定两次同样的值:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/no-listing-03-associated-functions/src/main.rs:here}}

View File

@ -4,4 +4,4 @@
> <br>
> commit c76f1b4d011fe59fc4f5e6f258070fc40d9921e4
本章介绍 **枚举***enumerations*),也被称作 *enums*。枚举允许你通过列举可能的 **成员***variants* 来定义一个类型。首先,我们会定义并使用一个枚举来展示它是如何连同数据一起编码信息的。接下来,我们会探索一个特别有用的枚举,叫做 `Option`,它代表一个值要么是某个值要么什么都不是。然后会讲到在 `match` 表达式中用模式匹配,针对不同的枚举值编写相应要执行的代码。最后会介绍 `if let`,另一个简洁方便处理代码中枚举的结构。
本章介绍 **枚举***enumerations*),也被称作 *enums*。枚举允许你通过列举可能的 **成员***variants*)来定义一个类型。首先,我们会定义并使用一个枚举来展示它是如何连同数据一起编码信息的。接下来,我们会探索一个特别有用的枚举,叫做 `Option`,它代表一个值要么是某个值要么什么都不是。然后会讲到在 `match` 表达式中用模式匹配,针对不同的枚举值编写相应要执行的代码。最后会介绍 `if let`,另一个简洁方便处理代码中枚举的结构。

View File

@ -4,7 +4,7 @@
> <br>
> commit c76f1b4d011fe59fc4f5e6f258070fc40d9921e4
结构体给予你将字段和数据聚合在一起的方法,像 `Rectangle` 结构体有 `width``height` 两个字段。而枚举给予你将一个值成为一个集合之一的方法。比如,我们想让 `Rectangle` 是一些形状的集合,包含 `Circle``Triangle` 。为了做到这个Rust提供了枚举类型。
结构体给予你将字段和数据聚合在一起的方法,像 `Rectangle` 结构体有 `width``height` 两个字段。而枚举给予你将一个值成为一个集合之一的方法。比如,我们想让 `Rectangle` 是一些形状的集合,包含 `Circle``Triangle` 。为了做到这个Rust 提供了枚举类型。
让我们看看一个需要诉诸于代码的场景,来考虑为何此时使用枚举更为合适且实用。假设我们要处理 IP 地址。目前被广泛使用的两个主要 IP 标准IPv4version four和 IPv6version six。这是我们的程序可能会遇到的所有可能的 IP 地址类型:所以可以 **枚举** 出所有可能的值,这也正是此枚举名字的由来。
@ -158,7 +158,7 @@ enum Option<T> {
{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-06-option-examples/src/main.rs:here}}
```
`some_number` 的类型是 `Option<i32>`。`some_char` 的类型是 `Option<char>`,这(与 `some_number`)是一个不同的类型。因为我们在 `Some` 成员中指定了值Rust 可以推断其类型。对于 `absent_number` Rust 需要我们指定 `Option` 整体的类型,因为编译器只通过 `None` 值无法推断出 `Some` 成员保存的值的类型。这里我们告诉 Rust 希望 `absent_number``Option<i32>` 类型的。
`some_number` 的类型是 `Option<i32>`。`some_char` 的类型是 `Option<char>`,这(与 `some_number`)是一个不同的类型。因为我们在 `Some` 成员中指定了值Rust 可以推断其类型。对于 `absent_number`Rust 需要我们指定 `Option` 整体的类型,因为编译器只通过 `None` 值无法推断出 `Some` 成员保存的值的类型。这里我们告诉 Rust 希望 `absent_number``Option<i32>` 类型的。
当有一个 `Some` 值时,我们就知道存在一个值,而这个值保存在 `Some` 中。当有个 `None` 值时,在某种意义上,它跟空值具有相同的意义:并没有一个有效的值。那么,`Option<T>` 为什么就比空值要好呢?

View File

@ -14,9 +14,9 @@
Rust 有许多功能可以让你管理代码的组织,包括哪些内容可以被公开,哪些内容作为私有部分,以及程序每个作用域中的名字。这些功能。这有时被称为 “模块系统the module system包括
* **包***Packages* Cargo 的一个功能,它允许你构建、测试和分享 crate。
* **包***Packages*Cargo 的一个功能,它允许你构建、测试和分享 crate。
* **Crates** :一个模块的树形结构,它形成了库或二进制项目。
* **模块***Modules*)和 **use** 允许你控制作用域和路径的私有性。
* **模块***Modules*)和 **use**:允许你控制作用域和路径的私有性。
* **路径***path*):一个命名例如结构体、函数或模块等项的方式
本章将会涵盖所有这些概念,讨论它们如何交互,并说明如何使用它们来管理作用域。到最后,你会对模块系统有深入的了解,并且能够像专业人士一样使用作用域!

View File

@ -6,7 +6,7 @@
模块系统的第一部分,我们将介绍包和 crate。
crate 是 Rust 在编译时最小的代码单位。如果你用 `rustc` 而不是 `cargo` 来编译一个文件(第一章我们这么做过),编译器还是会将那个文件认作一个 crate。 crate 可以包含模块,模块可以定义在其他文件,然后和 crate 一起编译,我们会在接下来的章节中遇到。
crate 是 Rust 在编译时最小的代码单位。如果你用 `rustc` 而不是 `cargo` 来编译一个文件(第一章我们这么做过),编译器还是会将那个文件认作一个 crate。crate 可以包含模块,模块可以定义在其他文件,然后和 crate 一起编译,我们会在接下来的章节中遇到。
crate 有两种形式:二进制项和库。*二进制项* 可以被编译为可执行程序,比如一个命令行程序或者一个服务器。它们必须有一个 `main` 函数来定义当程序被执行的时候所需要做的事情。目前我们所创建的 crate 都是二进制项。
@ -14,7 +14,7 @@ crate 有两种形式:二进制项和库。*二进制项* 可以被编译为
*crate root* 是一个源文件Rust 编译器以它为起始点,并构成你的 crate 的根模块(我们将在 [“定义模块来控制作用域与私有性”][modules] 一节深入解读)。
*包**package* 是提供一系列功能的一个或者多个 crate。一个包会包含一个 *Cargo.toml* 文件,阐述如何去构建这些 crate。Cargo 就是一个包含构建你代码的二进制项的包。Cargo 也包含这些二进制项所依赖的库。其他项目也能用 Cargo 库来实现与 Cargo 命令行程序一样的逻辑。
*包**package*)是提供一系列功能的一个或者多个 crate。一个包会包含一个 *Cargo.toml* 文件,阐述如何去构建这些 crate。Cargo 就是一个包含构建你代码的二进制项的包。Cargo 也包含这些二进制项所依赖的库。其他项目也能用 Cargo 库来实现与 Cargo 命令行程序一样的逻辑。
包中可以包含至多一个库 crate(library crate)。包中可以包含任意多个二进制 crate(binary crate),但是必须至少包含一个 crate无论是库的还是二进制的
@ -31,7 +31,7 @@ $ ls my-project/src
main.rs
```
运行了这条命令后,我们先用 `ls` (译者注:此命令为Linux平台的指令Windows下可用dir来看看 Cargo 给我们创建了什么Cargo 会给我们的包创建一个 *Cargo.toml* 文件。查看 *Cargo.toml* 的内容,会发现并没有提到 *src/main.rs*,因为 Cargo 遵循的一个约定:*src/main.rs* 就是一个与包同名的二进制 crate 的 crate 根。同样的Cargo 知道如果包目录中包含 *src/lib.rs*,则包带有与其同名的库 crate*src/lib.rs* 是 crate 根。crate 根文件将由 Cargo 传递给 `rustc` 来实际构建库或者二进制项目。
运行了这条命令后,我们先用 `ls` (译者注:此命令为 Linux 平台的指令Windows 下可用 dir来看看 Cargo 给我们创建了什么Cargo 会给我们的包创建一个 *Cargo.toml* 文件。查看 *Cargo.toml* 的内容,会发现并没有提到 *src/main.rs*,因为 Cargo 遵循的一个约定:*src/main.rs* 就是一个与包同名的二进制 crate 的 crate 根。同样的Cargo 知道如果包目录中包含 *src/lib.rs*,则包带有与其同名的库 crate*src/lib.rs* 是 crate 根。crate 根文件将由 Cargo 传递给 `rustc` 来实际构建库或者二进制项目。
在此,我们有了一个只包含 *src/main.rs* 的包,意味着它只含有一个名为 `my-project` 的二进制 crate。如果一个包同时含有 *src/main.rs**src/lib.rs*,则它有两个 crate一个二进制的和一个库的且名字都与包相同。通过将文件放在 *src/bin* 目录下,一个包可以拥有多个二进制 crate每个 *src/bin* 下的文件都会被编译成一个独立的二进制 crate。

View File

@ -12,20 +12,20 @@
这里我们提供一个简单的参考,用来解释模块、路径、`use`关键词和`pub`关键词如何在编译器中工作,以及大部分开发者如何组织他们的代码。我们将在本章节中举例说明每条规则,不过这是一个解释模块工作方式的良好参考。
- **从crate根节点开始**: 当编译一个crate, 编译器首先在crate根文件通常对于一个库crate而言是*src/lib.rs*对于一个二进制crate而言是*src/main.rs*)中寻找需要被编译的代码。
- **声明模块**: 在crate根文件中你可以声明一个新模块比如你用`mod garden`声明了一个叫做`garden`的模块。编译器会在下列路径中寻找模块代码:
- **从 crate 根节点开始**: 当编译一个 crate, 编译器首先在 crate 根文件(通常,对于一个库 crate 而言是*src/lib.rs*,对于一个二进制 crate 而言是*src/main.rs*)中寻找需要被编译的代码。
- **声明模块**: 在 crate 根文件中,你可以声明一个新模块;比如,你用`mod garden`声明了一个叫做`garden`的模块。编译器会在下列路径中寻找模块代码:
- 内联,在大括号中,当`mod garden`后方不是一个分号而是一个大括号
- 在文件 *src/garden.rs*
- 在文件 *src/garden/mod.rs*
- **声明子模块**: 在除了crate根节点以外的其他文件中你可以定义子模块。比如你可能在*src/garden.rs*中定义了`mod vegetables;`。编译器会在以父模块命名的目录中寻找子模块代码:
- 内联, 在大括号中,当`mod vegetables`后方不是一个分号而是一个大括号
- **声明子模块**: 在除了 crate 根节点以外的其他文件中,你可以定义子模块。比如,你可能在*src/garden.rs*中定义了`mod vegetables;`。编译器会在以父模块命名的目录中寻找子模块代码:
- 内联在大括号中,当`mod vegetables`后方不是一个分号而是一个大括号
- 在文件 *src/garden/vegetables.rs*
- 在文件 *src/garden/vegetables/mod.rs*
- **模块中的代码路径**: 一旦一个模块是你crate的一部分 你可以在隐私规则允许的前提下从同一个crate内的任意地方通过代码路径引用该模块的代码。举例而言一个garden vegetables模块下的`Asparagus`类型可以在`crate::garden::vegetables::Asparagus`被找到。
- **模块中的代码路径**: 一旦一个模块是你 crate 的一部分,你可以在隐私规则允许的前提下,从同一个 crate 内的任意地方,通过代码路径引用该模块的代码。举例而言,一个 garden vegetables 模块下的`Asparagus`类型可以在`crate::garden::vegetables::Asparagus`被找到。
- **私有 vs 公用**: 一个模块里的代码默认对其父模块私有。为了使一个模块公用,应当在声明时使用`pub mod`替代`mod`。为了使一个公用模块内部的成员公用,应当在声明前使用`pub`。
- **`use` 关键字**: 在一个作用域内,`use`关键字创建了一个成员的快捷方式,用来减少长路径的重复。在任何可以引用`crate::garden::vegetables::Asparagus`的作用域, 你可以通过 `use crate::garden::vegetables::Asparagus;`创建一个快捷方式,然后你就可以在作用域中只写`Asparagus`来使用该类型。
- **`use` 关键字**: 在一个作用域内,`use`关键字创建了一个成员的快捷方式,用来减少长路径的重复。在任何可以引用`crate::garden::vegetables::Asparagus`的作用域你可以通过 `use crate::garden::vegetables::Asparagus;`创建一个快捷方式,然后你就可以在作用域中只写`Asparagus`来使用该类型。
这里我们创建一个名为`backyard`的二进制crate来说明这些规则。该crate的路径同样命名为`backyard`,该路径包含了这些文件和目录:
这里我们创建一个名为`backyard`的二进制 crate 来说明这些规则。该 crate 的路径同样命名为`backyard`,该路径包含了这些文件和目录:
```text
backyard
@ -38,23 +38,23 @@ backyard
└── main.rs
```
这个例子中的crate根文件是*src/main.rs*,该文件包括了:
这个例子中的 crate 根文件是*src/main.rs*,该文件包括了:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,noplayground,ignore
{{#rustdoc_include ../listings/ch07-managing-growing-projects/quick-reference-example/src/main.rs}}
```
`pub mod garden;`行告诉编译器应该包含在*src/garden.rs*文件中发现的代码:
`pub mod garden;`行告诉编译器应该包含在*src/garden.rs*文件中发现的代码
<span class="filename">文件名: src/garden.rs</span>
<span class="filename">文件名src/garden.rs</span>
```rust,noplayground,ignore
{{#rustdoc_include ../listings/ch07-managing-growing-projects/quick-reference-example/src/garden.rs}}
```
在此处, `pub mod vegetables;`意味着在*src/garden/vegetables.rs*中的代码也应该被包括。这些代码是:
在此处, `pub mod vegetables;`意味着在*src/garden/vegetables.rs*中的代码也应该被包括。这些代码是
```rust,noplayground,ignore
{{#rustdoc_include ../listings/ch07-managing-growing-projects/quick-reference-example/src/garden/vegetables.rs}}
@ -70,7 +70,7 @@ backyard
我们可以将函数放置到嵌套的模块中,来使我们的 crate 结构与实际的餐厅结构相同。通过执行 `cargo new --lib restaurant`,来创建一个新的名为 `restaurant` 的库。然后将示例 7-1 中所罗列出来的代码放入 *src/lib.rs* 中,来定义一些模块和函数。
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch07-managing-growing-projects/listing-07-01/src/lib.rs}}
@ -100,6 +100,6 @@ crate
<span class="caption">示例 7-2: 示例 7-1 中代码的模块树</span>
这个树展示了一些模块是如何被嵌入到另一个模块的(例如,`hosting` 嵌套在 `front_of_house` 中)。这个树还展示了一些模块是互为 *兄弟**siblings* 的,这意味着它们定义在同一模块中(`hosting` 和 `serving` 被一起定义在 `front_of_house` 中)。继续沿用家庭关系的比喻,如果一个模块 A 被包含在模块 B 中,我们将模块 A 称为模块 B 的 *子**child*),模块 B 则是模块 A 的 *父**parent*)。注意,整个模块树都植根于名为 `crate` 的隐式模块下。
这个树展示了一些模块是如何被嵌入到另一个模块的(例如,`hosting` 嵌套在 `front_of_house` 中)。这个树还展示了一些模块是互为 *兄弟**siblings*)的,这意味着它们定义在同一模块中(`hosting` 和 `serving` 被一起定义在 `front_of_house` 中)。继续沿用家庭关系的比喻,如果一个模块 A 被包含在模块 B 中,我们将模块 A 称为模块 B 的 *子**child*),模块 B 则是模块 A 的 *父**parent*)。注意,整个模块树都植根于名为 `crate` 的隐式模块下。
这个模块树可能会令你想起电脑上文件系统的目录树;这是一个非常恰当的类比!就像文件系统的目录,你可以使用模块来组织你的代码。并且,就像目录中的文件,我们需要一种方法来找到模块。

View File

@ -14,7 +14,7 @@
让我们回到示例 7-1。我们如何调用 `add_to_waitlist` 函数?还是同样的问题,`add_to_waitlist` 函数的路径是什么?在示例 7-3 中,我们通过删除一些模块和函数,稍微简化了一下我们的代码。我们在 crate 根定义了一个新函数 `eat_at_restaurant`,并在其中展示调用 `add_to_waitlist` 函数的两种方法。`eat_at_restaurant` 函数是我们 crate 库的一个公共 API所以我们使用 `pub` 关键字来标记它。在 [“使用`pub`关键字暴露路径”][pub] 一节,我们将详细介绍 `pub`。注意,这个例子无法编译通过,我们稍后会解释原因。
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch07-managing-growing-projects/listing-07-03/src/lib.rs}}
@ -50,7 +50,7 @@ Rust 选择以这种方式来实现模块系统功能,因此默认隐藏内部
让我们回头看一下示例 7-4 的错误,它告诉我们 `hosting` 模块是私有的。我们想让父模块中的 `eat_at_restaurant` 函数可以访问子模块中的 `add_to_waitlist` 函数,因此我们使用 `pub` 关键字来标记 `hosting` 模块,如示例 7-5 所示。
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch07-managing-growing-projects/listing-07-05/src/lib.rs}}
@ -66,13 +66,13 @@ Rust 选择以这种方式来实现模块系统功能,因此默认隐藏内部
<span class="caption">示例 7-6: 构建示例 7-5 出现的编译器错误</span>
发生了什么?在 `mod hosting` 前添加了 `pub` 关键字,使其变成公有的。伴随着这种变化,如果我们可以访问 `front_of_house`,那我们也可以访问 `hosting`。但是 `hosting`_内容__contents_ 仍然是私有的;这表明使模块公有并不使其内容也是公有的。模块上的 `pub` 关键字只允许其父模块引用它。
发生了什么?在 `mod hosting` 前添加了 `pub` 关键字,使其变成公有的。伴随着这种变化,如果我们可以访问 `front_of_house`,那我们也可以访问 `hosting`。但是 `hosting`_内容__contents_仍然是私有的这表明使模块公有并不使其内容也是公有的。模块上的 `pub` 关键字只允许其父模块引用它。
示例 7-6 中的错误说,`add_to_waitlist` 函数是私有的。私有性规则不但应用于模块,还应用于结构体、枚举、函数和方法。
让我们继续将 `pub` 关键字放置在 `add_to_waitlist` 函数的定义之前,使其变成公有。如示例 7-7 所示。
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground,test_harness
{{#rustdoc_include ../listings/ch07-managing-growing-projects/listing-07-07/src/lib.rs}}
@ -94,7 +94,7 @@ Rust 选择以这种方式来实现模块系统功能,因此默认隐藏内部
考虑一下示例 7-8 中的代码,它模拟了厨师更正了一个错误订单,并亲自将其提供给客户的情况。`fix_incorrect_order` 函数通过指定的 `super` 起始的 `serve_order` 路径,来调用 `serve_order` 函数:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground,test_harness
{{#rustdoc_include ../listings/ch07-managing-growing-projects/listing-07-08/src/lib.rs}}
@ -108,7 +108,7 @@ Rust 选择以这种方式来实现模块系统功能,因此默认隐藏内部
我们还可以使用 `pub` 来设计公有的结构体和枚举,不过有一些额外的细节需要注意。如果我们在一个结构体定义的前面使用了 `pub` ,这个结构体会变成公有的,但是这个结构体的字段仍然是私有的。我们可以根据情况决定每个字段是否公有。在示例 7-9 中,我们定义了一个公有结构体 `back_of_house:Breakfast`,其中有一个公有字段 `toast` 和私有字段 `seasonal_fruit`。这个例子模拟的情况是,在一家餐馆中,顾客可以选择随餐附赠的面包类型,但是厨师会根据季节和库存情况来决定随餐搭配的水果。餐馆可用的水果变化是很快的,所以顾客不能选择水果,甚至无法看到他们将会得到什么水果。
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch07-managing-growing-projects/listing-07-09/src/lib.rs}}
@ -118,11 +118,11 @@ Rust 选择以这种方式来实现模块系统功能,因此默认隐藏内部
因为 `back_of_house::Breakfast` 结构体的 `toast` 字段是公有的,所以我们可以在 `eat_at_restaurant` 中使用点号来随意的读写 `toast` 字段。注意,我们不能在 `eat_at_restaurant` 中使用 `seasonal_fruit` 字段,因为 `seasonal_fruit` 是私有的。尝试去除那一行修改 `seasonal_fruit` 字段值的代码的注释,看看你会得到什么错误!
还请注意一点,因为 `back_of_house::Breakfast` 具有私有字段,所以这个结构体需要提供一个公共的关联函数来构造 `Breakfast` 的实例(这里我们命名为 `summer`)。如果 `Breakfast` 没有这样的函数,我们将无法在 `eat_at_restaurant` 中创建 `Breakfast` 实例,因为我们不能在 `eat_at_restaurant` 中设置私有字段 `seasonal_fruit` 的值。
还请注意一点,因为 `back_of_house::Breakfast` 具有私有字段,所以这个结构体需要提供一个公共的关联函数来构造 `Breakfast` 的实例 (这里我们命名为 `summer`)。如果 `Breakfast` 没有这样的函数,我们将无法在 `eat_at_restaurant` 中创建 `Breakfast` 实例,因为我们不能在 `eat_at_restaurant` 中设置私有字段 `seasonal_fruit` 的值。
与之相反,如果我们将枚举设为公有,则它的所有成员都将变为公有。我们只需要在 `enum` 关键字前面加上 `pub`,就像示例 7-10 展示的那样。
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch07-managing-growing-projects/listing-07-10/src/lib.rs}}

View File

@ -8,7 +8,7 @@
在示例 7-11 中,我们将 `crate::front_of_house::hosting` 模块引入了 `eat_at_restaurant` 函数的作用域,而我们只需要指定 `hosting::add_to_waitlist` 即可在 `eat_at_restaurant` 中调用 `add_to_waitlist` 函数。
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground,test_harness
{{#rustdoc_include ../listings/ch07-managing-growing-projects/listing-07-11/src/lib.rs}}
@ -20,7 +20,7 @@
你还可以使用 `use` 和相对路径来将一个项引入作用域。示例 7-12 展示了如何指定相对路径来取得与示例 7-11 中一样的行为。
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground,test_harness
{{#rustdoc_include ../listings/ch07-managing-growing-projects/listing-07-12/src/lib.rs}}
@ -32,7 +32,7 @@
在示例 7-11 中,你可能会比较疑惑,为什么我们是指定 `use crate::front_of_house::hosting` ,然后在 `eat_at_restaurant` 中调用 `hosting::add_to_waitlist` ,而不是通过指定一直到 `add_to_waitlist` 函数的 `use` 路径来得到相同的结果,如示例 7-13 所示。
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground,test_harness
{{#rustdoc_include ../listings/ch07-managing-growing-projects/listing-07-13/src/lib.rs}}
@ -44,7 +44,7 @@
另一方面,使用 `use` 引入结构体、枚举和其他项时,习惯是指定它们的完整路径。示例 7-14 展示了将 `HashMap` 结构体引入二进制 crate 作用域的习惯用法。
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch07-managing-growing-projects/listing-07-14/src/main.rs}}
@ -56,7 +56,7 @@
这个习惯用法有一个例外,那就是我们想使用 `use` 语句将两个具有相同名称的项带入作用域,因为 Rust 不允许这样做。示例 7-15 展示了如何将两个具有相同名称但不同父模块的 `Result` 类型引入作用域,以及如何引用它们。
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch07-managing-growing-projects/listing-07-15/src/lib.rs:here}}
@ -70,7 +70,7 @@
使用 `use` 将两个同名类型引入同一作用域这个问题还有另一个解决办法:在这个类型的路径后面,我们使用 `as` 指定一个新的本地名称或者别名。示例 7-16 展示了另一个编写示例 7-15 中代码的方法,通过 `as` 重命名其中一个 `Result` 类型。
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch07-managing-growing-projects/listing-07-16/src/lib.rs:here}}
@ -78,7 +78,7 @@
<span class="caption">示例 7-16: 使用 `as` 关键字重命名引入作用域的类型</span>
在第二个 `use` 语句中,我们选择 `IoResult` 作为 `std::io::Result` 的新名称,它与从 `std::fmt` 引入作用域的 `Result` 并不冲突。示例 7-15 和示例 7-16 都是惯用的,如何选择都取决于你!
在第二个 `use` 语句中,我们选择 `IoResult` 作为 `std::io::Result` 的新名称,它与从 `std::fmt` 引入作用域的 `Result` 并不冲突。示例 7-15 和示例 7-16 都是惯用的,如何选择都取决于你
### 使用 `pub use` 重导出名称
@ -86,7 +86,7 @@
示例 7-17 将示例 7-11 根模块中的 `use` 改为 `pub use`
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground,test_harness
{{#rustdoc_include ../listings/ch07-managing-growing-projects/listing-07-17/src/lib.rs}}
@ -102,7 +102,7 @@
在第二章中我们编写了一个猜猜看游戏。那个项目使用了一个外部包,`rand`,来生成随机数。为了在项目中使用 `rand`,在 *Cargo.toml* 中加入了如下行:
<span class="filename">文件名: Cargo.toml</span>
<span class="filename">文件名Cargo.toml</span>
```toml
{{#include ../listings/ch02-guessing-game-tutorial/listing-02-02/Cargo.toml:9:}}
@ -130,7 +130,7 @@ use std::collections::HashMap;
当需要引入很多定义于相同包或相同模块的项时,为每一项单独列出一行会占用源码很大的空间。例如猜猜看章节示例 2-4 中有两行 `use` 语句都从 `std` 引入项到作用域:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch07-managing-growing-projects/no-listing-01-use-std-unnested/src/main.rs:here}}
@ -138,7 +138,7 @@ use std::collections::HashMap;
相反,我们可以使用嵌套路径将相同的项在一行中引入作用域。这么做需要指定路径的相同部分,接着是两个冒号,接着是大括号中的各自不同的路径部分,如示例 7-18 所示。
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch07-managing-growing-projects/listing-07-18/src/main.rs:here}}
@ -150,7 +150,7 @@ use std::collections::HashMap;
我们可以在路径的任何层级使用嵌套路径,这在组合两个共享子路径的 `use` 语句时非常有用。例如,示例 7-19 中展示了两个 `use` 语句:一个将 `std::io` 引入作用域,另一个将 `std::io::Write` 引入作用域:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch07-managing-growing-projects/listing-07-19/src/lib.rs}}
@ -160,7 +160,7 @@ use std::collections::HashMap;
两个路径的相同部分是 `std::io`,这正是第一个路径。为了在一行 `use` 语句中引入这两个路径,可以在嵌套路径中使用 `self`,如示例 7-20 所示。
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch07-managing-growing-projects/listing-07-20/src/lib.rs}}

View File

@ -8,7 +8,7 @@
例如,我们从示例 7-17 开始,将 `front_of_house` 模块移动到属于它自己的文件 *src/front_of_house.rs* 中,通过改变 crate 根文件,使其包含示例 7-21 所示的代码。在这个例子中crate 根文件是 *src/lib.rs*,这也同样适用于以 *src/main.rs* 为 crate 根文件的二进制 crate 项。
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch07-managing-growing-projects/listing-07-21-and-22/src/lib.rs}}
@ -18,7 +18,7 @@
*src/front_of_house.rs* 会获取 `front_of_house` 模块的定义内容,如示例 7-22 所示。
<span class="filename">文件名: src/front_of_house.rs</span>
<span class="filename">文件名src/front_of_house.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch07-managing-growing-projects/listing-07-21-and-22/src/front_of_house.rs}}
@ -29,7 +29,7 @@
`mod front_of_house` 后使用分号,而不是代码块,这将告诉 Rust 在另一个与模块同名的文件中加载模块的内容。继续重构我们例子,将 `hosting` 模块也提取到其自己的文件中,仅对 *src/front_of_house.rs* 包含 `hosting` 模块的声明进行修改:
<span class="filename">文件名: src/front_of_house.rs</span>
<span class="filename">文件名src/front_of_house.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch07-managing-growing-projects/no-listing-02-extracting-hosting/src/front_of_house.rs}}
@ -37,7 +37,7 @@
接着我们创建一个 *src/front_of_house* 目录和一个包含 `hosting` 模块定义的 *src/front_of_house/hosting.rs* 文件:
<span class="filename">文件名: src/front_of_house/hosting.rs</span>
<span class="filename">文件名src/front_of_house/hosting.rs</span>
```rust
{{#rustdoc_include ../listings/ch07-managing-growing-projects/no-listing-02-extracting-hosting/src/front_of_house/hosting.rs}}

View File

@ -10,9 +10,9 @@
### 什么是字符串?
在开始深入这些方面之前,我们需要讨论一下术语 **字符串** 的具体意义。Rust 的核心语言中只有一种字符串类型字符串slice `str`,它通常以被借用的形式出现,`&str`。第四章讲到了 **字符串 slices**:它们是一些对储存在别处的 UTF-8 编码字符串数据的引用。举例来说由于字符串字面值被储存在程序的二进制输出中因此字符串字面值也是字符串slices。
在开始深入这些方面之前,我们需要讨论一下术语 **字符串** 的具体意义。Rust 的核心语言中只有一种字符串类型:字符串 slice `str`,它通常以被借用的形式出现,`&str`。第四章讲到了 **字符串 slices**:它们是一些对储存在别处的 UTF-8 编码字符串数据的引用。举例来说,由于字符串字面值被储存在程序的二进制输出中,因此字符串字面值也是字符串 slices。
称作 `String` 的类型是由标准库提供的而没有写进核心语言部分它是可增长的、可变的、有所有权的、UTF-8 编码的字符串类型。当 Rustacean 们谈到 Rust 的 “字符串”时,它们通常指的是 `String` 或字符串slice `&str` 类型,而不特指其中某一个。虽然本部分内容大多是关于 `String` 的,不过这两个类型在 Rust 标准库中都被广泛使用,`String` 和字符串 slices 都是 UTF-8 编码的。
称作 `String` 的类型是由标准库提供的而没有写进核心语言部分它是可增长的、可变的、有所有权的、UTF-8 编码的字符串类型。当 Rustacean 们谈到 Rust 的 “字符串”时,它们通常指的是 `String` 或字符串 slice `&str` 类型,而不特指其中某一个。虽然本部分内容大多是关于 `String` 的,不过这两个类型在 Rust 标准库中都被广泛使用,`String` 和字符串 slices 都是 UTF-8 编码的。
### 新建字符串
@ -152,7 +152,7 @@ fn add(self, s: &str) -> String {
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-14/src/main.rs:spanish}}
```
在这里,`len` 的值是 4 ,这意味着储存字符串 “Hola” 的 `Vec` 的长度是四个字节:这里每一个字母的 UTF-8 编码都占用一个字节。那下面这个例子又如何呢?(注意这个字符串中的首字母是西里尔字母的 Ze 而不是阿拉伯数字 3 。)
在这里,`len` 的值是 4这意味着储存字符串 “Hola” 的 `Vec` 的长度是四个字节:这里每一个字母的 UTF-8 编码都占用一个字节。那下面这个例子又如何呢?(注意这个字符串中的首字母是西里尔字母的 Ze 而不是阿拉伯数字 3。
```rust
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-14/src/main.rs:russian}}
@ -165,7 +165,7 @@ let hello = "Здравствуйте";
let answer = &hello[0];
```
我们已经知道 `answer` 不是第一个字符 `З`。当使用 UTF-8 编码时,`З` 的第一个字节 `208`,第二个是 `151`,所以 `answer` 实际上应该是 `208`,不过 `208` 自身并不是一个有效的字母。返回 `208` 可不是一个请求字符串第一个字母的人所希望看到的,不过它是 Rust 在字节索引 0 位置所能提供的唯一数据。用户通常不会想要一个字节值被返回,即便这个字符串只有拉丁字母: 即便 `&"hello"[0]` 是返回字节值的有效代码,它也应当返回 `104` 而不是 `h`
我们已经知道 `answer` 不是第一个字符 `З`。当使用 UTF-8 编码时,`З` 的第一个字节 `208`,第二个是 `151`,所以 `answer` 实际上应该是 `208`,不过 `208` 自身并不是一个有效的字母。返回 `208` 可不是一个请求字符串第一个字母的人所希望看到的,不过它是 Rust 在字节索引 0 位置所能提供的唯一数据。用户通常不会想要一个字节值被返回,即便这个字符串只有拉丁字母:即便 `&"hello"[0]` 是返回字节值的有效代码,它也应当返回 `104` 而不是 `h`
为了避免返回意外的值并造成不能立刻发现的 bugRust 根本不会编译这些代码,并在开发过程中及早杜绝了误会的发生。

View File

@ -8,7 +8,7 @@
> ### 对应 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]
@ -17,7 +17,7 @@
让我们在一个简单的程序中调用 `panic!`
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,should_panic,panics
{{#rustdoc_include ../listings/ch09-error-handling/no-listing-01-panic/src/main.rs}}
@ -37,7 +37,7 @@
让我们来看看另一个因为我们代码中的 bug 引起的别的库中 `panic!` 的例子,而不是直接的宏调用。示例 9-1 有一些尝试通过索引访问 vector 中元素的例子:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,should_panic,panics
{{#rustdoc_include ../listings/ch09-error-handling/listing-09-01/src/main.rs}}

View File

@ -19,7 +19,7 @@ enum Result<T, E> {
让我们调用一个返回 `Result` 的函数,因为它可能会失败:如示例 9-3 所示打开一个文件:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch09-error-handling/listing-09-03/src/main.rs}}
@ -47,7 +47,7 @@ enum Result<T, E> {
我们需要在示例 9-3 的代码中增加根据 `File::open` 返回值进行不同处理的逻辑。示例 9-4 展示了一个使用基本工具处理 `Result` 的例子:第六章学习过的 `match` 表达式。
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,should_panic
{{#rustdoc_include ../listings/ch09-error-handling/listing-09-04/src/main.rs}}
@ -72,7 +72,7 @@ enum Result<T, E> {
示例 9-4 中的代码不管 `File::open` 是因为什么原因失败都会 `panic!`。我们真正希望的是对不同的错误原因采取不同的行为:如果 `File::open `因为文件不存在而失败,我们希望创建这个文件并返回新文件的句柄。如果 `File::open` 因为任何其他原因失败,例如没有打开文件的权限,我们仍然希望像示例 9-4 那样 `panic!`。让我们看看示例 9-5其中 `match` 增加了另一个分支:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch09-error-handling/listing-09-05/src/main.rs}}
@ -113,7 +113,7 @@ enum Result<T, E> {
`match` 能够胜任它的工作,不过它可能有点冗长并且不总是能很好的表明其意图。`Result<T, E>` 类型定义了很多辅助方法来处理各种情况。其中之一叫做 `unwrap`,它的实现就类似于示例 9-4 中的 `match` 语句。如果 `Result` 值是成员 `Ok``unwrap` 会返回 `Ok` 中的值。如果 `Result` 是成员 `Err``unwrap` 会为我们调用 `panic!`。这里是一个实践 `unwrap` 的例子:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,should_panic
{{#rustdoc_include ../listings/ch09-error-handling/no-listing-04-unwrap/src/main.rs}}
@ -129,7 +129,7 @@ src/libcore/result.rs:906:4
还有另一个类似于 `unwrap` 的方法它还允许我们选择 `panic!` 的错误信息:`expect`。使用 `expect` 而不是 `unwrap` 并提供一个好的错误信息可以表明你的意图并更易于追踪 panic 的根源。`expect` 的语法看起来像这样:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,should_panic
{{#rustdoc_include ../listings/ch09-error-handling/no-listing-05-expect/src/main.rs}}
@ -150,7 +150,7 @@ thread 'main' panicked at 'Failed to open hello.txt: Error { repr: Os { code:
例如,示例 9-6 展示了一个从文件中读取用户名的函数。如果文件不存在或不能读取,这个函数会将这些错误返回给调用它的代码:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#include ../listings/ch09-error-handling/listing-09-06/src/main.rs:here}}
@ -172,7 +172,7 @@ thread 'main' panicked at 'Failed to open hello.txt: Error { repr: Os { code:
示例 9-7 展示了一个 `read_username_from_file` 的实现,它实现了与示例 9-6 中的代码相同的功能,不过这个实现使用了 `?` 运算符:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#include ../listings/ch09-error-handling/listing-09-07/src/main.rs:here}}
@ -188,7 +188,7 @@ thread 'main' panicked at 'Failed to open hello.txt: Error { repr: Os { code:
`?` 运算符消除了大量样板代码并使得函数的实现更简单。我们甚至可以在 `?` 之后直接使用链式方法调用来进一步缩短代码,如示例 9-8 所示:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#include ../listings/ch09-error-handling/listing-09-08/src/main.rs:here}}
@ -200,7 +200,7 @@ thread 'main' panicked at 'Failed to open hello.txt: Error { repr: Os { code:
说到编写这个函数的不同方法,甚至还有一个更短的写法:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#include ../listings/ch09-error-handling/listing-09-09/src/main.rs:here}}
@ -245,7 +245,7 @@ thread 'main' panicked at 'Failed to open hello.txt: Error { repr: Os { code:
目前为止,我们所使用的所有 `main` 函数都返回 `()`。`main` 函数是特殊的因为它是可执行程序的入口点和退出点,为了使程序能正常工作,其可以返回的类型是有限制的。
幸运的是 `main` 函数也可以返回 `Result<(), E>` 示例 9-12 中的代码来自示例 9-10 不过修改了 `main` 的返回值为 `Result<(), Box<dyn Error>>` 并在结尾增加了一个 `Ok(())` 作为返回值。这段代码可以编译:
幸运的是 `main` 函数也可以返回 `Result<(), E>`,示例 9-12 中的代码来自示例 9-10 不过修改了 `main` 的返回值为 `Result<(), Box<dyn Error>>` 并在结尾增加了一个 `Ok(())` 作为返回值。这段代码可以编译:
```rust,ignore
{{#rustdoc_include ../listings/ch09-error-handling/listing-09-12/src/main.rs}}

View File

@ -6,7 +6,7 @@
那么,该如何决定何时应该 `panic!` 以及何时应该返回 `Result` 呢?如果代码 panic就没有恢复的可能。你可以选择对任何错误场景都调用 `panic!`,不管是否有可能恢复,不过这样就是你代替调用者决定了这是不可恢复的。选择返回 `Result` 值的话,就将选择权交给了调用者,而不是代替他们做出决定。调用者可能会选择以符合他们场景的方式尝试恢复,或者也可能干脆就认为 `Err` 是不可恢复的,所以他们也可能会调用 `panic!` 并将可恢复的错误变成了不可恢复的错误。因此返回 `Result` 是定义可能会失败的函数的一个好的默认选择。
在一些类似示例、原型代码prototype code和测试中 panic 比返回 `Result` 更为合适,不过他们并不常见。让我们讨论一下为何在示例、代码原型和测试中,以及那些人们认为不会失败而编译器不这么看的情况下, panic 是合适的。章节最后会总结一些在库代码中如何决定是否要 panic 的通用指导原则。
在一些类似示例、原型代码prototype code和测试中panic 比返回 `Result` 更为合适不过他们并不常见。让我们讨论一下为何在示例、代码原型和测试中以及那些人们认为不会失败而编译器不这么看的情况下panic 是合适的。章节最后会总结一些在库代码中如何决定是否要 panic 的通用指导原则。
### 示例、代码原型和测试都非常适合 panic
@ -70,7 +70,7 @@
接着,我们实现了一个借用了 `self` 的方法 `value`,它没有任何其他参数并返回一个 `i32`。这类方法有时被称为 *getter*,因为它的目的就是返回对应字段的数据。这样的公有方法是必要的,因为 `Guess` 结构体的 `value` 字段是私有的。私有的字段 `value` 是很重要的,这样使用 `Guess` 结构体的代码将不允许直接设置 `value` 的值:调用者 **必须** 使用 `Guess::new` 方法来创建一个 `Guess` 的实例,这就确保了不会存在一个 `value` 没有通过 `Guess::new` 函数的条件检查的 `Guess`
于是,一个接收(或返回) 1 到 100 之间数字的函数就可以声明为接收(或返回) `Guess`的实例,而不是 `i32`,同时其函数体中也无需进行任何额外的检查。
于是一个接收或返回1 到 100 之间数字的函数就可以声明为接收(或返回) `Guess`的实例,而不是 `i32`,同时其函数体中也无需进行任何额外的检查。
## 总结

View File

@ -20,7 +20,7 @@
考虑一下这个寻找列表中最大值的小程序,如示例 10-1 所示:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-01/src/main.rs:here}}
@ -32,7 +32,7 @@
如果需要在两个不同的列表中寻找最大值,我们可以重复示例 10-1 中的代码,这样程序中就会存在两段相同逻辑的代码,如示例 10-2 所示:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-02/src/main.rs}}
@ -46,7 +46,7 @@
在示例 10-3 的程序中将寻找最大值的代码提取到了一个叫做 `largest` 的函数中。这不同于示例 10-1 中的代码只能在一个特定的列表中找到最大的数字,这个程序可以在两个不同的列表中找到最大的数字。
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-03/src/main.rs:here}}

View File

@ -12,7 +12,7 @@
回到 `largest` 函数,示例 10-4 中展示了两个函数,它们的功能都是寻找 slice 中最大值。
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-04/src/main.rs:here}}
@ -34,7 +34,7 @@ fn largest<T>(list: &[T]) -> T {
示例 10-5 中的 `largest` 函数在它的签名中使用了泛型,统一了两个实现。该示例也展示了如何调用 `largest` 函数,把 `i32` 值的 slice 或 `char` 值的 slice 传给它。请注意这些代码还不能编译,不过稍后在本章会解决这个问题。
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-05/src/main.rs}}
@ -56,7 +56,7 @@ fn largest<T>(list: &[T]) -> T {
同样也可以用 `<>` 语法来定义结构体,它包含一个或多个泛型参数类型字段。示例 10-6 展示了如何定义和使用一个可以存放任何类型的 `x``y` 坐标值的结构体 `Point`
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-06/src/main.rs}}
@ -68,7 +68,7 @@ fn largest<T>(list: &[T]) -> T {
注意 `Point<T>` 的定义中只使用了一个泛型类型,这个定义表明结构体 `Point<T>` 对于一些类型 `T` 是泛型的,而且字段 `x``y` **都是** 相同类型的,无论它具体是何类型。如果尝试创建一个有不同类型值的 `Point<T>` 的实例,像示例 10-7 中的代码就不能编译:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-07/src/main.rs}}
@ -84,7 +84,7 @@ fn largest<T>(list: &[T]) -> T {
如果想要定义一个 `x``y` 可以有不同类型且仍然是泛型的 `Point` 结构体,我们可以使用多个泛型类型参数。在示例 10-8 中,我们修改 `Point` 的定义为拥有两个泛型类型 `T``U`。其中字段 `x``T` 类型的,而字段 `y``U` 类型的:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-08/src/main.rs}}
@ -124,7 +124,7 @@ enum Result<T, E> {
在为结构体和枚举实现方法时(像第五章那样),一样也可以用泛型。示例 10-9 中展示了示例 10-6 中定义的结构体 `Point<T>`,和在其上实现的名为 `x` 的方法。
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-09/src/main.rs}}
@ -148,7 +148,7 @@ enum Result<T, E> {
结构体定义中的泛型类型参数并不总是与结构体方法签名中使用的泛型是同一类型。示例 10-11 中为 `Point` 结构体使用了泛型类型 `X1``Y1`,为 `mixup` 方法签名使用了 `X2``Y2` 来使得示例更加清楚。这个方法用 `self``Point` 类型的 `x` 值(类型 `X1`)和参数的 `Point` 类型的 `y` 值(类型 `Y2`)来创建一个新 `Point` 类型的实例:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-11/src/main.rs}}
@ -179,7 +179,7 @@ let float = Some(5.0);
编译器生成的单态化版本的代码看起来像这样,并包含将泛型 `Option<T>` 替换为编译器创建的具体定义后的用例代码:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
enum Option_i32 {

View File

@ -16,7 +16,7 @@
我们想要创建一个名为 `aggregator` 的多媒体聚合库用来显示可能储存在 `NewsArticle``Tweet` 实例中的数据的总结。每一个结构体都需要的行为是他们是能够被总结的,这样的话就可以调用实例的 `summarize` 方法来请求总结。示例 10-12 中展示了一个表现这个概念的公有 `Summary` trait 的定义:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-12/src/lib.rs}}
@ -34,7 +34,7 @@ trait 体中可以有多个方法:一行一个方法签名且都以分号结
现在我们定义了 `Summary` trait 的签名,接着就可以在多媒体聚合库中实现这个类型了。示例 10-13 中展示了 `NewsArticle` 结构体上 `Summary` trait 的一个实现,它使用标题、作者和创建的位置作为 `summarize` 的返回值。对于 `Tweet` 结构体,我们选择将 `summarize` 定义为用户名后跟推文的全部文本作为返回值,并假设推文内容已经被限制为 280 字符以内。
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-13/src/lib.rs:here}}
@ -54,7 +54,7 @@ trait 体中可以有多个方法:一行一个方法签名且都以分号结
其他依赖 `aggregator` crate 的 crate 也可以将 `Summary` 引入作用域以便为其自己的类型实现该 trait。实现 trait 时需要注意的一个限制是,只有当至少一个 trait 或者要实现 trait 的类型位于 crate 的本地作用域时,才能为该类型实现 trait。例如可以为 `aggregator` crate 的自定义类型 `Tweet` 实现如标准库中的 `Display` trait这是因为 `Tweet` 类型位于 `aggregator` crate 本地的作用域中。类似地,也可以在 `aggregator` crate 中为 `Vec<T>` 实现 `Summary`,这是因为 `Summary` trait 位于 `aggregator` crate 本地作用域中。
但是不能为外部类型实现外部 trait。例如不能在 `aggregator` crate 中为 `Vec<T>` 实现 `Display` trait。这是因为 `Display``Vec<T>` 都定义于标准库中,它们并不位于 `aggregator` crate 本地作用域中。这个限制是被称为 **相干性***coherence* 的程序属性的一部分,或者更具体的说是 **孤儿规则***orphan rule*),其得名于不存在父类型。这条规则确保了其他人编写的代码不会破坏你代码,反之亦然。没有这条规则的话,两个 crate 可以分别对相同类型实现相同的 trait而 Rust 将无从得知应该使用哪一个实现。
但是不能为外部类型实现外部 trait。例如不能在 `aggregator` crate 中为 `Vec<T>` 实现 `Display` trait。这是因为 `Display``Vec<T>` 都定义于标准库中,它们并不位于 `aggregator` crate 本地作用域中。这个限制是被称为 **相干性***coherence*)的程序属性的一部分,或者更具体的说是 **孤儿规则***orphan rule*),其得名于不存在父类型。这条规则确保了其他人编写的代码不会破坏你代码,反之亦然。没有这条规则的话,两个 crate 可以分别对相同类型实现相同的 trait而 Rust 将无从得知应该使用哪一个实现。
### 默认实现
@ -62,7 +62,7 @@ trait 体中可以有多个方法:一行一个方法签名且都以分号结
示例 10-14 中展示了如何为 `Summary` trait 的 `summarize` 方法指定一个默认的字符串值,而不是像示例 10-12 中那样只是定义方法签名:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-14/src/lib.rs:here}}
@ -221,7 +221,7 @@ fn some_function<T, U>(t: &T, u: &U) -> i32
为了只对实现了 `Copy` 的类型调用这些代码,可以在 `T` 的 trait bounds 中增加 `Copy`!示例 10-15 中展示了一个可以编译的泛型版本的 `largest` 函数的完整代码,只要传递给 `largest` 的 slice 值的类型实现了 `PartialOrd` **和** `Copy` 这两个 trait例如 `i32``char`
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-15/src/main.rs}}

View File

@ -57,7 +57,7 @@ Rust 编译器有一个 **借用检查器***borrow checker*),它比较作
让我们来编写一个返回两个字符串 slice 中较长者的函数。这个函数获取两个字符串 slice 并返回一个字符串 slice。一旦我们实现了 `longest` 函数,示例 10-20 中的代码应该会打印出 `The longest string is abcd`
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-20/src/main.rs}}
@ -69,7 +69,7 @@ Rust 编译器有一个 **借用检查器***borrow checker*),它比较作
如果尝试像示例 10-21 中那样实现 `longest` 函数,它并不能编译:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-21/src/main.rs:here}}
@ -108,7 +108,7 @@ Rust 编译器有一个 **借用检查器***borrow checker*),它比较作
现在来看看 `longest` 函数的上下文中的生命周期。就像泛型类型参数,泛型生命周期参数需要声明在函数名和参数列表间的尖括号中。
在这个签名中我们想要表达的限制是所有(两个)参数和返回的引用的生命周期是相关的,也就是这两个参数和返回的引用存活的一样久。就像示例 10-22 中在每个引用中都加上了 `'a` 那样:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-22/src/main.rs:here}}
@ -129,7 +129,7 @@ Rust 编译器有一个 **借用检查器***borrow checker*),它比较作
让我们看看如何通过传递拥有不同具体生命周期的引用来限制 `longest` 函数的使用。示例 10-23 是一个很直观的例子。
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-23/src/main.rs:here}}
@ -141,7 +141,7 @@ Rust 编译器有一个 **借用检查器***borrow checker*),它比较作
接下来,让我们尝试另外一个例子,该例子揭示了 `result` 的引用的生命周期必须是两个参数中较短的那个。以下代码将 `result` 变量的声明移动出内部作用域,但是将 `result``string2` 变量的赋值语句一同留在内部作用域中。接着,使用了变量 `result``println!` 也被移动到内部作用域之外。注意示例 10-24 中的代码不能通过编译:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-24/src/main.rs:here}}
@ -165,7 +165,7 @@ Rust 编译器有一个 **借用检查器***borrow checker*),它比较作
指定生命周期参数的正确方式依赖函数实现的具体功能。例如,如果将 `longest` 函数的实现修改为总是返回第一个参数而不是最长的字符串 slice就不需要为参数 `y` 指定一个生命周期。如下代码将能够编译:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/no-listing-08-only-one-reference-with-lifetime/src/main.rs:here}}
@ -175,7 +175,7 @@ Rust 编译器有一个 **借用检查器***borrow checker*),它比较作
当从函数返回一个引用,返回值的生命周期参数需要与一个参数的生命周期参数相匹配。如果返回的引用 **没有** 指向任何一个参数,那么唯一的可能就是它指向一个函数内部创建的值,它将会是一个悬垂引用,因为它将会在函数结束时离开作用域。尝试考虑这个并不能编译的 `longest` 函数实现:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/no-listing-09-unrelated-lifetime/src/main.rs:here}}
@ -195,7 +195,7 @@ Rust 编译器有一个 **借用检查器***borrow checker*),它比较作
目前为止,我们只定义过有所有权类型的结构体。接下来,我们将定义包含引用的结构体,不过这需要为结构体定义中的每一个引用添加生命周期注解。示例 10-25 中有一个存放了一个字符串 slice 的结构体 `ImportantExcerpt`
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-25/src/main.rs}}
@ -211,7 +211,7 @@ Rust 编译器有一个 **借用检查器***borrow checker*),它比较作
现在我们已经知道了每一个引用都有一个生命周期,而且我们需要为那些使用了引用的函数或结构体指定生命周期。然而,第四章的示例 4-9 中有一个函数,如示例 10-26 所示,它没有生命周期注解却能编译成功:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-26/src/main.rs:here}}
@ -241,7 +241,7 @@ fn first_word<'a>(s: &'a str) -> &'a str {
第二条规则是如果只有一个输入生命周期参数,那么它被赋予所有输出生命周期参数:`fn foo<'a>(x: &'a i32) -> &'a i32`。
第三条规则是如果方法有多个输入生命周期参数并且其中一个参数是 `&self``&mut self`,说明是个对象的方法(method)(译者注: 这里涉及rust的面向对象参见17章),那么所有输出生命周期参数被赋予 `self` 的生命周期。第三条规则使得方法更容易读写,因为只需更少的符号。
第三条规则是如果方法有多个输入生命周期参数并且其中一个参数是 `&self``&mut self`,说明是个对象的方法 (method)(译者注:这里涉及 rust 的面向对象参见 17 章),那么所有输出生命周期参数被赋予 `self` 的生命周期。第三条规则使得方法更容易读写,因为只需更少的符号。
假设我们自己就是编译器。并应用这些规则来计算示例 10-26 中 `first_word` 函数签名中的引用的生命周期。开始时签名中的引用并没有关联任何生命周期:

View File

@ -30,7 +30,7 @@ $ cd adder
adder 库中 `src/lib.rs` 的内容应该看起来如示例 11-1 所示:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch11-writing-automated-tests/listing-11-01/src/lib.rs}}
@ -62,7 +62,7 @@ Cargo 编译并运行了测试。在 `Compiling`、`Finished` 和 `Running` 这
让我们改变测试的名称并看看这如何改变测试的输出。给 `it_works` 函数起个不同的名字,比如 `exploration`,像这样:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch11-writing-automated-tests/no-listing-01-changing-test-name/src/lib.rs}}
@ -76,7 +76,7 @@ Cargo 编译并运行了测试。在 `Compiling`、`Finished` 和 `Running` 这
让我们增加另一个测试,不过这一次是一个会失败的测试!当测试函数中出现 panic 时测试就失败了。每一个测试都在一个新线程中运行,当主线程发现测试线程异常了,就将对应测试标记为失败。第九章讲到了最简单的造成 panic 的方法:调用 `panic!` 宏。写入新测试 `another` 后, `src/lib.rs` 现在看起来如示例 11-3 所示:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,panics,noplayground
{{#rustdoc_include ../listings/ch11-writing-automated-tests/listing-11-03/src/lib.rs:here}}
@ -104,7 +104,7 @@ Cargo 编译并运行了测试。在 `Compiling`、`Finished` 和 `Running` 这
回忆一下第五章中,示例 5-15 中有一个 `Rectangle` 结构体和一个 `can_hold` 方法,在示例 11-5 中再次使用他们。将他们放进 *src/lib.rs* 并使用 `assert!` 宏编写一些测试。
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch11-writing-automated-tests/listing-11-05/src/lib.rs:here}}
@ -114,7 +114,7 @@ Cargo 编译并运行了测试。在 `Compiling`、`Finished` 和 `Running` 这
`can_hold` 方法返回一个布尔值,这意味着它完美符合 `assert!` 宏的使用场景。在示例 11-6 中,让我们编写一个 `can_hold` 方法的测试来作为练习,这里创建一个长为 8 宽为 7 的 `Rectangle` 实例,并假设它可以放得下另一个长为 5 宽为 1 的 `Rectangle` 实例:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch11-writing-automated-tests/listing-11-06/src/lib.rs:here}}
@ -132,7 +132,7 @@ Cargo 编译并运行了测试。在 `Compiling`、`Finished` 和 `Running` 这
它确实通过了!再来增加另一个测试,这一回断言一个更小的矩形不能放下一个更大的矩形:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch11-writing-automated-tests/no-listing-02-adding-another-rectangle-test/src/lib.rs:here}}
@ -164,7 +164,7 @@ Cargo 编译并运行了测试。在 `Compiling`、`Finished` 和 `Running` 这
示例 11-7 中,让我们编写一个对其参数加二并返回结果的函数 `add_two`。接着使用 `assert_eq!` 宏测试这个函数。
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch11-writing-automated-tests/listing-11-07/src/lib.rs}}
@ -206,7 +206,7 @@ Cargo 编译并运行了测试。在 `Compiling`、`Finished` 和 `Running` 这
例如,比如说有一个根据人名进行问候的函数,而我们希望测试将传递给函数的人名显示在输出中:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch11-writing-automated-tests/no-listing-05-greeter/src/lib.rs}}
@ -248,7 +248,7 @@ Cargo 编译并运行了测试。在 `Compiling`、`Finished` 和 `Running` 这
示例 11-8 展示了一个检查 `Guess::new` 是否按照我们的期望出错的测试:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch11-writing-automated-tests/listing-11-08/src/lib.rs}}
@ -278,7 +278,7 @@ Cargo 编译并运行了测试。在 `Compiling`、`Finished` 和 `Running` 这
然而 `should_panic` 测试结果可能会非常含糊不清,因为它只是告诉我们代码并没有产生 panic。`should_panic` 甚至在一些不是我们期望的原因而导致 panic 时也会通过。为了使 `should_panic` 测试结果更精确,我们可以给 `should_panic` 属性增加一个可选的 `expected` 参数。测试工具会确保错误信息中包含其提供的文本。例如,考虑示例 11-9 中修改过的 `Guess`,这里 `new` 函数根据其值是过大还或者过小而提供不同的 panic 信息:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch11-writing-automated-tests/listing-11-09/src/lib.rs:here}}

View File

@ -10,7 +10,7 @@
### 并行或连续的运行测试
当运行多个测试时, Rust 默认使用线程来并行运行。这意味着测试会更快地运行完毕,所以你可以更快的得到代码能否工作的反馈。因为测试是在同时运行的,你应该确保测试不能相互依赖,或依赖任何共享的状态,包括依赖共享的环境,比如当前工作目录或者环境变量。
当运行多个测试时Rust 默认使用线程来并行运行。这意味着测试会更快地运行完毕,所以你可以更快的得到代码能否工作的反馈。因为测试是在同时运行的,你应该确保测试不能相互依赖,或依赖任何共享的状态,包括依赖共享的环境,比如当前工作目录或者环境变量。
举个例子,每一个测试都运行一些代码,假设这些代码都在硬盘上创建一个 *test-output.txt* 文件并写入一些数据。接着每一个测试都读取文件中的数据并断言这个文件包含特定的值,而这个值在每个测试中都是不同的。因为所有测试都是同时运行的,一个测试可能会在另一个测试读写文件过程中修改了文件。那么第二个测试就会失败,并不是因为代码不正确,而是因为测试并行运行时相互干扰。一个解决方案是使每一个测试读写不同的文件;另一个解决方案是一次运行一个测试。
@ -28,7 +28,7 @@ $ cargo test -- --test-threads=1
例如,示例 11-10 有一个无意义的函数,它打印出其参数的值并接着返回 10。接着还有一个会通过的测试和一个会失败的测试
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,panics,noplayground
{{#rustdoc_include ../listings/ch11-writing-automated-tests/listing-11-10/src/lib.rs}}
@ -62,7 +62,7 @@ $ cargo test -- --show-output
为了展示如何运行部分测试,示例 11-11 为 `add_two` 函数创建了三个测试,我们可以选择具体运行哪一个:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch11-writing-automated-tests/listing-11-11/src/lib.rs}}
@ -102,7 +102,7 @@ $ cargo test -- --show-output
有时一些特定的测试执行起来是非常耗费时间的,所以在大多数运行 `cargo test` 的时候希望能排除他们。虽然可以通过参数列举出所有希望运行的测试来做到,也可以使用 `ignore` 属性来标记耗时的测试并排除他们,如下所示:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch11-writing-automated-tests/no-listing-11-ignore-a-test/src/lib.rs}}

View File

@ -18,19 +18,19 @@
回忆本章第一部分新建的 `adder` 项目Cargo 为我们生成了如下代码:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch11-writing-automated-tests/listing-11-01/src/lib.rs}}
```
上述代码就是自动生成的测试模块。`cfg` 属性代表 *configuration* ,它告诉 Rust 其之后的项只应该被包含进特定配置选项中。在这个例子中,配置选项是 `test`,即 Rust 所提供的用于编译和运行测试的配置选项。通过使用 `cfg` 属性Cargo 只会在我们主动使用 `cargo test` 运行测试时才编译测试代码。这包括测试模块中可能存在的帮助函数, 以及标注为 #[test] 的函数。
上述代码就是自动生成的测试模块。`cfg` 属性代表 *configuration* ,它告诉 Rust 其之后的项只应该被包含进特定配置选项中。在这个例子中,配置选项是 `test`,即 Rust 所提供的用于编译和运行测试的配置选项。通过使用 `cfg` 属性Cargo 只会在我们主动使用 `cargo test` 运行测试时才编译测试代码。这包括测试模块中可能存在的帮助函数,以及标注为 #[test] 的函数。
#### 测试私有函数
测试社区中一直存在关于是否应该对私有函数直接进行测试的论战而在其他语言中想要测试私有函数是一件困难的甚至是不可能的事。不过无论你坚持哪种测试意识形态Rust 的私有性规则确实允许你测试私有函数。考虑示例 11-12 中带有私有函数 `internal_adder` 的代码:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch11-writing-automated-tests/listing-11-12/src/lib.rs}}
@ -42,7 +42,7 @@
### 集成测试
在 Rust 中,集成测试对于你需要测试的库来说完全是外部的。同其他使用库的代码一样使用库文件,也就是说它们只能调用一部分库中的公有 API 。集成测试的目的是测试库的多个部分能否一起正常工作。一些单独能正确运行的代码单元集成在一起也可能会出现问题,所以集成测试的覆盖率也是很重要的。为了创建集成测试,你需要先创建一个 *tests* 目录。
在 Rust 中,集成测试对于你需要测试的库来说完全是外部的。同其他使用库的代码一样使用库文件,也就是说它们只能调用一部分库中的公有 API。集成测试的目的是测试库的多个部分能否一起正常工作。一些单独能正确运行的代码单元集成在一起也可能会出现问题所以集成测试的覆盖率也是很重要的。为了创建集成测试你需要先创建一个 *tests* 目录。
#### *tests* 目录
@ -50,7 +50,7 @@
让我们来创建一个集成测试。保留示例 11-12 中 *src/lib.rs* 的代码。创建一个 *tests* 目录,新建一个文件 *tests/integration_test.rs*,并输入示例 11-13 中的代码。
<span class="filename">文件名: tests/integration_test.rs</span>
<span class="filename">文件名tests/integration_test.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch11-writing-automated-tests/listing-11-13/tests/integration_test.rs}}
@ -60,7 +60,7 @@
与单元测试不同,我们需要在文件顶部添加 `use adder`。这是因为每一个 `tests` 目录中的测试文件都是完全独立的 crate所以需要在每一个文件中导入库。
并不需要将 *tests/integration_test.rs* 中的任何代码标注为 `#[cfg(test)]``tests` 文件夹在 Cargo 中是一个特殊的文件夹, Cargo 只会在运行 `cargo test` 时编译这个目录中的文件。现在就运行 `cargo test` 试试:
并不需要将 *tests/integration_test.rs* 中的任何代码标注为 `#[cfg(test)]``tests` 文件夹在 Cargo 中是一个特殊的文件夹Cargo 只会在运行 `cargo test` 时编译这个目录中的文件。现在就运行 `cargo test` 试试:
```console
{{#include ../listings/ch11-writing-automated-tests/listing-11-13/output.txt}}
@ -88,7 +88,7 @@
当你有一些在多个集成测试文件都会用到的帮助函数,而你尝试按照第七章 “将模块移动到其他文件” 部分的步骤将他们提取到一个通用的模块中时, *tests* 目录中不同文件的行为就会显得很明显。例如,如果我们可以创建 一个*tests/common.rs* 文件并创建一个名叫 `setup` 的函数,我们希望这个函数能被多个测试文件的测试函数调用:
<span class="filename">文件名: tests/common.rs</span>
<span class="filename">文件名tests/common.rs</span>
```rust
{{#rustdoc_include ../listings/ch11-writing-automated-tests/no-listing-12-shared-test-code-problem/tests/common.rs}}
@ -106,7 +106,7 @@
一旦拥有了 *tests/common/mod.rs*,就可以将其作为模块以便在任何集成测试文件中使用。这里是一个 *tests/integration_test.rs* 中调用 `setup` 函数的 `it_adds_two` 测试的例子:
<span class="filename">文件名: tests/integration_test.rs</span>
<span class="filename">文件名tests/integration_test.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch11-writing-automated-tests/no-listing-13-fix-shared-test-code-problem/tests/integration_test.rs}}

View File

@ -8,7 +8,7 @@
Rust 的运行速度、安全性、单二进制文件输出和跨平台支持使其成为创建命令行程序的绝佳选择,所以我们的项目将创建一个我们自己版本的经典命令行工具:`grep`。grep 是 “**G**lobally search a **R**egular **E**xpression and **P**rint.” 的首字母缩写。`grep` 最简单的使用场景是在特定文件中搜索指定字符串。为此,`grep` 获取一个文件名和一个字符串作为参数,接着读取文件并找到其中包含字符串参数的行,然后打印出这些行。
在这个过程中,我们会展示如何让我们的命令行工具利用很多命令行工具中用到的终端功能。读取环境变量来使得用户可以配置工具的行为。打印到标准错误控制流(`stderr` 而不是标准输出(`stdout`),例如这样用户可以选择将成功输出重定向到文件中的同时仍然在屏幕上显示错误信息。
在这个过程中,我们会展示如何让我们的命令行工具利用很多命令行工具中用到的终端功能。读取环境变量来使得用户可以配置工具的行为。打印到标准错误控制流(`stderr`)而不是标准输出(`stdout`),例如这样用户可以选择将成功输出重定向到文件中的同时仍然在屏幕上显示错误信息。
一位 Rust 社区的成员Andrew Gallant已经创建了一个功能完整且非常快速的 `grep` 版本,叫做 `ripgrep`。相比之下,我们的 `grep` 版本将非常简单,本章将教会你一些帮助理解像 `ripgrep` 这样真实项目的背景知识。

View File

@ -26,7 +26,7 @@ $ cargo run searchstring example-filename.txt
使用示例 12-1 中的代码来读取任何传递给 `minigrep` 的命令行参数并将其收集到一个 vector 中。
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-01/src/main.rs}}
@ -58,7 +58,7 @@ $ cargo run searchstring example-filename.txt
打印出参数 vector 中的值展示了程序可以访问指定为命令行参数的值。现在需要将这两个参数的值保存进变量这样就可以在程序的余下部分使用这些值了。让我们如示例 12-2 这样做:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,should_panic,noplayground
{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-02/src/main.rs}}

View File

@ -6,7 +6,7 @@
现在我们要增加读取由 `filename` 命令行参数指定的文件的功能。首先,需要一个用来测试的示例文件:用来确保 `minigrep` 正常工作的最好的文件是拥有多行少量文本且有一些重复单词的文件。示例 12-3 是一首艾米莉·狄金森Emily Dickinson的诗它正适合这个工作在项目根目录创建一个文件 `poem.txt`,并输入诗 "I'm nobody! Who are you?"
<span class="filename">文件名: poem.txt</span>
<span class="filename">文件名poem.txt</span>
```text
{{#include ../listings/ch12-an-io-project/listing-12-03/poem.txt}}
@ -16,7 +16,7 @@
创建完这个文件之后,修改 *src/main.rs* 并增加如示例 12-4 所示的打开文件的代码:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,should_panic,noplayground
{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-04/src/main.rs:here}}

View File

@ -37,7 +37,7 @@
首先,我们将解析参数的功能提取到一个 `main` 将会调用的函数中,为将命令行解析逻辑移动到 *src/lib.rs* 中做准备。示例 12-5 中展示了新 `main` 函数的开头,它调用了新函数 `parse_config`。目前它仍将定义在 *src/main.rs* 中:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-05/src/main.rs:here}}
@ -59,7 +59,7 @@
示例 12-6 展示了 `parse_config` 函数的改进。
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,should_panic,noplayground
{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-06/src/main.rs:here}}
@ -86,7 +86,7 @@
所以现在 `parse_config` 函数的目的是创建一个 `Config` 实例,我们可以将 `parse_config` 从一个普通函数变为一个叫做 `new` 的与结构体关联的函数。做出这个改变使得代码更符合习惯:可以像标准库中的 `String` 调用 `String::new` 来创建一个该类型的实例那样,将 `parse_config` 变为一个与 `Config` 关联的 `new` 函数。示例 12-7 展示了需要做出的修改:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,should_panic,noplayground
{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-07/src/main.rs:here}}
@ -110,7 +110,7 @@
在示例 12-8 中,在 `new` 函数中增加了一个检查在访问索引 `1``2` 之前检查 slice 是否足够长。如果 slice 不够长,我们使用一个更好的错误信息 panic 而不是 `index out of bounds` 信息:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-08/src/main.rs:here}}
@ -134,7 +134,7 @@
示例 12-9 展示了为了返回 `Result``Config::new` 的返回值和函数体中所需的改变。注意这还不能编译,直到下一个示例同时也更新了 `main` 之后。
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-09/src/main.rs:here}}
@ -152,7 +152,7 @@
为了处理错误情况并打印一个对用户友好的信息,我们需要像示例 12-10 那样更新 `main` 函数来处理现在 `Config::new` 返回的 `Result`。另外还需要手动实现原先由 `panic!`负责的工作,即以非零错误码退出命令行工具的工作。非零的退出状态是一个惯例信号,用来告诉调用程序的进程:该程序以错误状态退出了。
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-10/src/main.rs:here}}
@ -176,7 +176,7 @@
示例 12-11 展示了提取出来的 `run` 函数。目前我们只进行小的增量式的提取函数的改进。我们仍将在 *src/main.rs* 中定义这个函数:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-11/src/main.rs:here}}
@ -190,7 +190,7 @@
通过将剩余的逻辑分离进 `run` 函数而不是留在 `main` 中,就可以像示例 12-9 中的 `Config::new` 那样改进错误处理。不再通过 `expect` 允许程序 panic`run` 函数将会在出错时返回一个 `Result<T, E>`。这让我们进一步以一种对用户友好的方式统一 `main` 中的错误处理。示例 12-12 展示了 `run` 签名和函数体中的改变:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-12/src/main.rs:here}}
@ -218,7 +218,7 @@ Rust 提示我们的代码忽略了 `Result` 值,它可能表明这里存在
我们将检查错误并使用类似示例 12-10 中 `Config::new` 处理错误的技术来处理他们,不过有一些细微的不同:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch12-an-io-project/no-listing-01-handling-errors-in-main/src/main.rs:here}}
@ -241,7 +241,7 @@ Rust 提示我们的代码忽略了 `Result` 值,它可能表明这里存在
现在 *src/lib.rs* 的内容应该看起来像示例 12-13为了简洁省略了函数体。注意直到下一个示例修改完 *src/main.rs* 之后,代码还不能编译:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-13/src/lib.rs:here}}
@ -253,7 +253,7 @@ Rust 提示我们的代码忽略了 `Result` 值,它可能表明这里存在
现在需要在 *src/main.rs* 中将移动到 *src/lib.rs* 的代码引入二进制 crate 的作用域中,如示例 12-14 所示:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-14/src/main.rs:here}}

View File

@ -21,7 +21,7 @@
去掉 *src/lib.rs**src/main.rs* 中用于检查程序行为的 `println!` 语句,因为不再真正需要他们了。接着我们会像 [第十一章][ch11-anatomy] 那样增加一个 `test` 模块和一个测试函数。测试函数指定了 `search` 函数期望拥有的行为:它会获取一个需要查询的字符串和用来查询的文本,并只会返回包含请求的文本行。示例 12-15 展示了这个测试,它还不能编译:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-15/src/lib.rs:here}}
@ -33,7 +33,7 @@
我们还不能运行这个测试并看到它失败,因为它甚至都还不能编译:`search` 函数还不存在呢!我们将增加足够的代码来使其能够编译:一个总是会返回空 vector 的 `search` 函数定义,如示例 12-16 所示。然后这个测试应该能够编译并因为空 vector 并不匹配一个包含一行 `"safe, fast, productive."` 的 vector 而失败。
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-16/src/lib.rs:here}}
@ -79,7 +79,7 @@ Rust 不可能知道我们需要的是哪一个参数,所以需要告诉它。
Rust 有一个有助于一行一行遍历字符串的方法,出于方便它被命名为 `lines`,它如示例 12-17 这样工作。注意这还不能编译:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-17/src/lib.rs:here}}
@ -93,7 +93,7 @@ Rust 有一个有助于一行一行遍历字符串的方法,出于方便它被
接下来将会增加检查当前行是否包含查询字符串的功能。幸运的是,字符串类型为此也有一个叫做 `contains` 的实用方法!如示例 12-18 所示在 `search` 函数中加入 `contains` 方法调用。注意这仍然不能编译:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-18/src/lib.rs:here}}
@ -105,7 +105,7 @@ Rust 有一个有助于一行一行遍历字符串的方法,出于方便它被
我们还需要一个方法来存储包含查询字符串的行。为此可以在 `for` 循环之前创建一个可变的 vector 并调用 `push` 方法在 vector 中存放一个 `line`。在 `for` 循环之后,返回这个 vector如示例 12-19 所示:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-19/src/lib.rs:here}}
@ -127,7 +127,7 @@ Rust 有一个有助于一行一行遍历字符串的方法,出于方便它被
现在 `search` 函数是可以工作并测试通过了的,我们需要实际在 `run` 函数中调用 `search`。需要将 `config.query` 值和 `run` 从文件中读取的 `contents` 传递给 `search` 函数。接着 `run` 会打印出 `search` 返回的每一行:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch12-an-io-project/no-listing-02-using-search-in-run/src/lib.rs:here}}

View File

@ -4,13 +4,13 @@
> <br>
> commit 9c0fa2714859738ff73cbbb829592e4c037d7e46
我们将增加一个额外的功能来改进 `minigrep`:用户可以通过设置环境变量来设置搜索是否是大小写敏感的 。当然,我们也可以将其设计为一个命令行参数并要求用户每次需要时都加上它,不过在这里我们将使用环境变量。这允许用户设置环境变量一次之后在整个终端会话中所有的搜索都将是大小写不敏感的。
我们将增加一个额外的功能来改进 `minigrep`:用户可以通过设置环境变量来设置搜索是否是大小写敏感的。当然,我们也可以将其设计为一个命令行参数并要求用户每次需要时都加上它,不过在这里我们将使用环境变量。这允许用户设置环境变量一次之后在整个终端会话中所有的搜索都将是大小写不敏感的。
### 编写一个大小写不敏感 `search` 函数的失败测试
我们希望增加一个新函数 `search_case_insensitive`,并将会在设置了环境变量时调用它。这里将继续遵循 TDD 过程,其第一步是再次编写一个失败测试。我们将为新的大小写不敏感搜索函数新增一个测试函数,并将老的测试函数从 `one_result` 改名为 `case_sensitive` 来更清楚的表明这两个测试的区别,如示例 12-20 所示:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-20/src/lib.rs:here}}
@ -26,7 +26,7 @@
`search_case_insensitive` 函数,如示例 12-21 所示,将与 `search` 函数基本相同。唯一的区别是它会将 `query` 变量和每一 `line` 都变为小写,这样不管输入参数是大写还是小写,在检查该行是否包含查询字符串时都会是小写。
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-21/src/lib.rs:here}}
@ -48,7 +48,7 @@
好的!现在,让我们在 `run` 函数中实际调用新 `search_case_insensitive` 函数。首先,我们将在 `Config` 结构体中增加一个配置项来切换大小写敏感和大小写不敏感搜索。增加这些字段会导致编译错误,因为我们还没有在任何地方初始化这些字段:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-22/src/lib.rs:here}}
@ -56,7 +56,7 @@
这里增加了 `case_sensitive` 字符来存放一个布尔值。接着我们需要 `run` 函数检查 `case_sensitive` 字段的值并使用它来决定是否调用 `search` 函数或 `search_case_insensitive` 函数,如示例 12-22 所示。注意这还不能编译:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-22/src/lib.rs:there}}
@ -66,7 +66,7 @@
最后需要实际检查环境变量。处理环境变量的函数位于标准库的 `env` 模块中,所以我们需要在 *src/lib.rs* 的开头增加一个 `use std::env;` 行将这个模块引入作用域中。接着在 `Config::new` 中使用 `env` 模块的 `var` 方法来检查一个叫做 `CASE_INSENSITIVE` 的环境变量,如示例 12-23 所示:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-23/src/lib.rs:here}}

View File

@ -32,7 +32,7 @@ Problem parsing arguments: not enough arguments
让我们如示例 12-24 所示的代码改变错误信息是如何被打印的。得益于本章早些时候的重构,所有打印错误信息的代码都位于 `main` 一个函数中。标准库提供了 `eprintln!` 宏来打印到标准错误流,所以将两个调用 `println!` 打印错误信息的位置替换为 `eprintln!`
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-24/src/main.rs:here}}
@ -57,7 +57,7 @@ $ cargo run to poem.txt > output.txt
我们并不会在终端看到任何输出,同时 `output.txt` 将会包含其结果:
<span class="filename">文件名: output.txt</span>
<span class="filename">文件名output.txt</span>
```text
Are you nobody, too?

View File

@ -14,7 +14,7 @@ Rust 的 **闭包***closures*)是可以保存在一个变量中或作为参
这里将通过调用 `simulated_expensive_calculation` 函数来模拟调用假定的算法,如示例 13-1 所示,它会打印出 `calculating slowly...`,等待两秒,并接着返回传递给它的数字:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-01/src/main.rs:here}}
@ -31,7 +31,7 @@ Rust 的 **闭包***closures*)是可以保存在一个变量中或作为参
程序的输出将会是建议的锻炼计划。示例 13-2 展示了我们将要使用的 `main` 函数:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-02/src/main.rs:here}}
@ -43,7 +43,7 @@ Rust 的 **闭包***closures*)是可以保存在一个变量中或作为参
现在有了执行上下文,让我们编写算法。示例 13-3 中的 `generate_workout` 函数包含本例中我们最关心的 app 业务逻辑。本例中余下的代码修改都将在这个函数中进行:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-03/src/main.rs:here}}
@ -65,7 +65,7 @@ Rust 的 **闭包***closures*)是可以保存在一个变量中或作为参
有多种方法可以重构此程序。我们首先尝试的是将重复的 `simulated_expensive_calculation` 函数调用提取到一个变量中,如示例 13-4 所示:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-04/src/main.rs:here}}
@ -81,7 +81,7 @@ Rust 的 **闭包***closures*)是可以保存在一个变量中或作为参
不同于总是在 `if` 块之前调用 `simulated_expensive_calculation` 函数并储存其结果,我们可以定义一个闭包并将其储存在变量中,如示例 13-5 所示。实际上可以选择将整个 `simulated_expensive_calculation` 函数体移动到这里引入的闭包中:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-05/src/main.rs:here}}
@ -91,13 +91,13 @@ Rust 的 **闭包***closures*)是可以保存在一个变量中或作为参
闭包定义是 `expensive_closure` 赋值的 `=` 之后的部分。闭包的定义以一对竖线(`|`)开始,在竖线中指定闭包的参数;之所以选择这个语法是因为它与 Smalltalk 和 Ruby 的闭包定义类似。这个闭包有一个参数 `num`;如果有多于一个参数,可以使用逗号分隔,比如 `|param1, param2|`
参数之后是存放闭包体的大括号 —— 如果闭包体只有一行则大括号是可以省略的。大括号之后闭包的结尾,需要用于 `let` 语句的分号。因为闭包体的最后一行没有分号(正如函数体一样),所以闭包体(`num`)最后一行的返回值作为调用闭包时的返回值
参数之后是存放闭包体的大括号 —— 如果闭包体只有一行则大括号是可以省略的。大括号之后闭包的结尾,需要用于 `let` 语句的分号。因为闭包体的最后一行没有分号(正如函数体一样),所以闭包体(`num`)最后一行的返回值作为调用闭包时的返回值。
注意这个 `let` 语句意味着 `expensive_closure` 包含一个匿名函数的 **定义**,不是调用匿名函数的 **返回值**。回忆一下使用闭包的原因是我们需要在一个位置定义代码,储存代码,并在之后的位置实际调用它;期望调用的代码现在储存在 `expensive_closure` 中。
定义了闭包之后,可以改变 `if` 块中的代码来调用闭包以执行代码并获取结果值。调用闭包类似于调用函数;指定存放闭包定义的变量名并后跟包含期望使用的参数的括号,如示例 13-6 所示:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-06/src/main.rs:here}}
@ -119,7 +119,7 @@ Rust 的 **闭包***closures*)是可以保存在一个变量中或作为参
类似于变量,如果相比严格的必要性你更希望增加明确性并变得更啰嗦,可以选择增加类型注解;为示例 13-5 中定义的闭包标注类型将看起来像示例 13-7 中的定义:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-07/src/main.rs:here}}
@ -140,7 +140,7 @@ let add_one_v4 = |x| x + 1 ;
闭包定义会为每个参数和返回值推断一个具体类型。例如,示例 13-8 中展示了仅仅将参数作为返回值的简短的闭包定义。除了作为示例的目的这个闭包并不是很实用。注意其定义并没有增加任何类型注解:如果尝试调用闭包两次,第一次使用 `String` 类型作为参数而第二次使用 `u32`,则会得到一个错误:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-08/src/main.rs:here}}
@ -158,7 +158,7 @@ let add_one_v4 = |x| x + 1 ;
### 使用带有泛型和 `Fn` trait 的闭包
回到我们的健身计划生成 app ,在示例 13-6 中的代码仍然把慢计算闭包调用了比所需更多的次数。解决这个问题的一个方法是在全部代码中的每一个需要多个慢计算闭包结果的地方,可以将结果保存进变量以供复用,这样就可以使用变量而不是再次调用闭包。但是这样就会有很多重复的保存结果变量的地方。
回到我们的健身计划生成 app在示例 13-6 中的代码仍然把慢计算闭包调用了比所需更多的次数。解决这个问题的一个方法是在全部代码中的每一个需要多个慢计算闭包结果的地方,可以将结果保存进变量以供复用,这样就可以使用变量而不是再次调用闭包。但是这样就会有很多重复的保存结果变量的地方。
幸运的是,还有另一个可用的方案。可以创建一个存放闭包和调用闭包结果的结构体。该结构体只会在需要结果时执行闭包,并会缓存结果值,这样余下的代码就不必再负责保存结果并可以复用该值。你可能见过这种模式被称 *memoization**lazy evaluation* *(惰性求值)*
@ -170,7 +170,7 @@ let add_one_v4 = |x| x + 1 ;
示例 13-9 展示了存放了闭包和一个 Option 结果值的 `Cacher` 结构体的定义:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-09/src/main.rs:here}}
@ -186,7 +186,7 @@ let add_one_v4 = |x| x + 1 ;
刚才讨论的有关 `value` 字段逻辑定义于示例 13-10
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-10/src/main.rs:here}}
@ -204,7 +204,7 @@ let add_one_v4 = |x| x + 1 ;
示例 13-11 展示了如何在示例 13-6 的 `generate_workout` 函数中利用 `Cacher` 结构体:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-11/src/main.rs:here}}
@ -246,7 +246,7 @@ let add_one_v4 = |x| x + 1 ;
示例 13-12 有一个储存在 `equal_to_x` 变量中闭包的例子,它使用了闭包环境中的变量 `x`
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-12/src/main.rs}}
@ -258,7 +258,7 @@ let add_one_v4 = |x| x + 1 ;
函数则不能做到同样的事,如果尝试如下例子,它并不能编译:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch13-functional-features/no-listing-02-functions-cant-capture/src/main.rs}}
@ -280,7 +280,7 @@ let add_one_v4 = |x| x + 1 ;
* `FnMut` 获取可变的借用值所以可以改变其环境
* `Fn` 从其环境获取不可变的借用值
当创建一个闭包时Rust 根据其如何使用环境中变量来推断我们希望如何引用环境。由于所有闭包都可以被调用至少一次,所以所有闭包都实现了 `FnOnce` 。那些并没有移动被捕获变量的所有权到闭包内的闭包也实现了 `FnMut` ,而不需要对被捕获的变量进行可变访问的闭包则也实现了 `Fn` 在示例 13-12 中,`equal_to_x` 闭包不可变的借用了 `x`(所以 `equal_to_x` 具有 `Fn` trait因为闭包体只需要读取 `x` 的值。
当创建一个闭包时Rust 根据其如何使用环境中变量来推断我们希望如何引用环境。由于所有闭包都可以被调用至少一次,所以所有闭包都实现了 `FnOnce` 。那些并没有移动被捕获变量的所有权到闭包内的闭包也实现了 `FnMut` ,而不需要对被捕获的变量进行可变访问的闭包则也实现了 `Fn` 。在示例 13-12 中,`equal_to_x` 闭包不可变的借用了 `x`(所以 `equal_to_x` 具有 `Fn` trait因为闭包体只需要读取 `x` 的值。
如果你希望强制闭包获取其使用的环境值的所有权,可以在参数列表前使用 `move` 关键字。这个技巧在将闭包传递给新线程以便将数据移动到新线程中时最为实用。
@ -288,7 +288,7 @@ let add_one_v4 = |x| x + 1 ;
第十六章讨论并发时会展示更多 `move` 闭包的例子,不过现在这里修改了示例 13-12 中的代码(作为演示),在闭包定义中增加 `move` 关键字并使用 vector 代替整型,因为整型可以被拷贝而不是移动;注意这些代码还不能编译:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch13-functional-features/no-listing-03-move-closures/src/main.rs}}

View File

@ -26,7 +26,7 @@
在标准库中没有提供迭代器的语言中,我们可能会使用一个从 0 开始的索引变量,使用这个变量索引 vector 中的值,并循环增加其值直到达到 vector 的元素数量。
迭代器为我们处理了所有这些逻辑,这减少了重复代码并消除了潜在的混乱。另外,迭代器的实现方式提供了对多种不同的序列使用相同逻辑的灵活性,而不仅仅是像 vector 这样可索引的数据结构.让我们看看迭代器是如何做到这些的。
迭代器为我们处理了所有这些逻辑,这减少了重复代码并消除了潜在的混乱。另外,迭代器的实现方式提供了对多种不同的序列使用相同逻辑的灵活性,而不仅仅是像 vector 这样可索引的数据结构让我们看看迭代器是如何做到这些的。
### `Iterator` trait 和 `next` 方法
@ -48,7 +48,7 @@ pub trait Iterator {
可以直接调用迭代器的 `next` 方法;示例 13-15 有一个测试展示了重复调用由 vector 创建的迭代器的 `next` 方法所得到的值:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-15/src/lib.rs:here}}
@ -67,7 +67,7 @@ pub trait Iterator {
这些调用 `next` 方法的方法被称为 **消费适配器***consuming adaptors*),因为调用他们会消耗迭代器。一个消费适配器的例子是 `sum` 方法。这个方法获取迭代器的所有权并反复调用 `next` 来遍历迭代器,因而会消费迭代器。当其遍历每一个项时,它将每一个项加总到一个总和并在迭代完成时返回总和。示例 13-16 有一个展示 `sum` 方法使用的测试:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-16/src/lib.rs:here}}
@ -81,9 +81,9 @@ pub trait Iterator {
`Iterator` trait 中定义了另一类方法,被称为 **迭代器适配器***iterator adaptors*),他们允许我们将当前迭代器变为不同类型的迭代器。可以链式调用多个迭代器适配器。不过因为所有的迭代器都是惰性的,必须调用一个消费适配器方法以便获取迭代器适配器调用的结果。
示例 13-17 展示了一个调用迭代器适配器方法 `map` 的例子,该 `map` 方法使用闭包来调用每个元素以生成新的迭代器。 这里的闭包创建了一个新的迭代器,对其中 vector 中的每个元素都被加 1。不过这些代码会产生一个警告
示例 13-17 展示了一个调用迭代器适配器方法 `map` 的例子,该 `map` 方法使用闭包来调用每个元素以生成新的迭代器。这里的闭包创建了一个新的迭代器,对其中 vector 中的每个元素都被加 1。不过这些代码会产生一个警告
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,not_desired_behavior
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-17/src/main.rs:here}}
@ -103,7 +103,7 @@ pub trait Iterator {
在示例 13-18 中,我们将遍历由 `map` 调用生成的迭代器的结果收集到一个 vector 中,它将会含有原始 vector 中每个元素加 1 的结果:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-18/src/main.rs:here}}
@ -119,7 +119,7 @@ pub trait Iterator {
示例 13-19 展示了使用 `filter` 和一个捕获环境中变量 `shoe_size` 的闭包,这样闭包就可以遍历一个 `Shoe` 结构体集合以便只返回指定大小的鞋子:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-19/src/lib.rs}}
@ -143,7 +143,7 @@ pub trait Iterator {
示例 13-20 有一个 `Counter` 结构体定义和一个创建 `Counter` 实例的关联函数 `new`
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-20/src/lib.rs}}
@ -155,7 +155,7 @@ pub trait Iterator {
接下来将为 `Counter` 类型实现 `Iterator` trait通过定义 `next` 方法来指定使用迭代器时的行为,如示例 13-21 所示:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-21/src/lib.rs:here}}
@ -171,7 +171,7 @@ pub trait Iterator {
一旦实现了 `Iterator` trait我们就有了一个迭代器示例 13-22 展示了一个测试用来演示使用 `Counter` 结构体的迭代器功能,通过直接调用 `next` 方法,正如示例 13-15 中从 vector 创建的迭代器那样:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-22/src/lib.rs:here}}
@ -187,7 +187,7 @@ pub trait Iterator {
例如,出于某种原因我们希望获取 `Counter` 实例产生的值,将这些值与另一个 `Counter` 实例在省略了第一个值之后产生的值配对,将每一对值相乘,只保留那些可以被三整除的结果,然后将所有保留的结果相加,这可以如示例 13-23 中的测试这样做:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-23/src/lib.rs:here}}

View File

@ -10,7 +10,7 @@
在示例 12-6 中,我们增加了一些代码获取一个 `String` slice 并创建一个 `Config` 结构体的实例,他们索引 slice 中的值并克隆这些值以便 `Config` 结构体可以拥有这些值。在示例 13-24 中重现了第十二章结尾示例 12-23 中 `Config::new` 函数的实现:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch13-functional-features/listing-12-23-reproduced/src/lib.rs:ch13}}
@ -30,7 +30,7 @@
打开 I/O 项目的 *src/main.rs* 文件,它看起来应该像这样:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch13-functional-features/listing-12-24-reproduced/src/main.rs:ch13}}
@ -38,7 +38,7 @@
修改第十二章结尾示例 12-24 中的 `main` 函数的开头为示例 13-25 中的代码。在更新 `Config::new` 之前这些代码还不能编译:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-25/src/main.rs:here}}
@ -50,7 +50,7 @@
接下来需要更新 `Config::new` 的定义。在 I/O 项目的 *src/lib.rs* 中,将 `Config::new` 的签名改为如示例 13-26 所示。这仍然不能编译因为我们还需更新函数体:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-26/src/lib.rs:here}}
@ -64,7 +64,7 @@
接下来,我们将修改 `Config::new` 的内容。标准库文档还提到 `std::env::Args` 实现了 `Iterator` trait因此我们知道可以对其调用 `next` 方法!示例 13-27 更新了示例 12-23 中的代码,以使用 `next` 方法:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-27/src/lib.rs:here}}
@ -78,7 +78,7 @@
I/O 项目中其他可以利用迭代器的地方是 `search` 函数,示例 13-28 中重现了第十二章结尾示例 12-19 中此函数的定义:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-19/src/lib.rs:ch13}}
@ -88,7 +88,7 @@ I/O 项目中其他可以利用迭代器的地方是 `search` 函数,示例 13
可以通过使用迭代器适配器方法来编写更简明的代码。这也避免了一个可变的中间 `results` vector 的使用。函数式编程风格倾向于最小化可变状态的数量来使代码更简洁。去掉可变状态可能会使得将来进行并行搜索的增强变得更容易,因为我们不必管理 `results` vector 的并发访问。示例 13-29 展示了该变化:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-29/src/lib.rs:here}}

View File

@ -15,9 +15,9 @@ test bench_search_iter ... bench: 19,234,900 ns/iter (+/- 657,200)
结果迭代器版本还要稍微快一点!这里我们将不会查看性能测试的代码,我们的目的并不是为了证明他们是完全等同的,而是得出一个怎样比较这两种实现方式性能的基本思路。
对于一个更全面的性能测试,将会检查不同长度的文本、不同的搜索单词、不同长度的单词和所有其他的可变情况。这里所要表达的是:迭代器,作为一个高级的抽象,被编译成了与手写的底层代码大体一致性能代码。迭代器是 Rust 的 **零成本抽象***zero-cost abstractions*之一它意味着抽象并不会引入运行时开销它与本贾尼·斯特劳斯特卢普C++ 的设计和实现者)在 “Foundations of C++”2012 中所定义的 **零开销***zero-overhead*)如出一辙:
对于一个更全面的性能测试,将会检查不同长度的文本、不同的搜索单词、不同长度的单词和所有其他的可变情况。这里所要表达的是:迭代器,作为一个高级的抽象,被编译成了与手写的底层代码大体一致性能代码。迭代器是 Rust 的 **零成本抽象***zero-cost abstractions*之一它意味着抽象并不会引入运行时开销它与本贾尼·斯特劳斯特卢普C++ 的设计和实现者)在 “Foundations of C++”2012中所定义的 **零开销***zero-overhead*)如出一辙:
> In general, C++ implementations obey the zero-overhead principle: What you dont use, you dont pay for. And further: What you do use, you couldnt hand code any better.
> 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.
>
> - Bjarne Stroustrup "Foundations of C++"
>

View File

@ -21,7 +21,7 @@ $ cargo build --release
当项目的 *Cargo.toml* 文件中没有任何 `[profile.*]` 部分的时候Cargo 会对每一个配置都采用默认设置。通过增加任何希望定制的配置对应的 `[profile.*]` 部分,我们可以选择覆盖任意默认设置的子集。例如,如下是 `dev``release` 配置的 `opt-level` 设置的默认值:
<span class="filename">文件名: Cargo.toml</span>
<span class="filename">文件名Cargo.toml</span>
```toml
[profile.dev]
@ -35,7 +35,7 @@ opt-level = 3
我们可以选择通过在 *Cargo.toml* 增加不同的值来覆盖任何默认设置。比如,如果我们想要在开发配置中使用级别 1 的优化,则可以在 *Cargo.toml* 中增加这两行:
<span class="filename">文件名: Cargo.toml</span>
<span class="filename">文件名Cargo.toml</span>
```toml
[profile.dev]

View File

@ -13,7 +13,7 @@ Rust 和 Cargo 有一些帮助他人更方便找到和使用你发布的包的
文档注释使用三斜杠 `///` 而不是两斜杆以支持 Markdown 注解来格式化文本。文档注释就位于需要文档的项的之前。示例 14-1 展示了一个 `my_crate` crate 中 `add_one` 函数的文档注释,
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch14-more-about-cargo/listing-14-01/src/lib.rs}}
@ -60,7 +60,7 @@ test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; fini
作为一个例子,如果我们希望增加描述包含 `add_one` 函数的 `my_crate` crate 目的的文档,可以在 _src/lib.rs_ 开头增加以 `//!` 开头的注释,如示例 14-2 所示:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch14-more-about-cargo/listing-14-02/src/lib.rs:here}}
@ -88,7 +88,7 @@ test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; fini
例如,假设我们创建了一个描述美术信息的库 `art`。这个库中包含了一个有两个枚举 `PrimaryColor``SecondaryColor` 的模块 `kinds`,以及一个包含函数 `mix` 的模块 `utils`,如示例 14-3 所示:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground,test_harness
{{#rustdoc_include ../listings/ch14-more-about-cargo/listing-14-03/src/lib.rs:here}}
@ -106,7 +106,7 @@ test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; fini
另一个依赖这个库的 crate 需要 `use` 语句来导入 `art` 中的项,这包含指定其当前定义的模块结构。示例 14-4 展示了一个使用 `art` crate 中 `PrimaryColor``mix` 项的 crate 的例子:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch14-more-about-cargo/listing-14-04/src/main.rs}}
@ -118,7 +118,7 @@ test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; fini
为了从公有 API 中去掉 crate 的内部组织,我们可以采用示例 14-3 中的 `art` crate 并增加 `pub use` 语句来重导出项到顶层结构,如示例 14-5 所示:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch14-more-about-cargo/listing-14-05/src/lib.rs:here}}
@ -134,7 +134,7 @@ test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; fini
`art` crate 的用户仍然可以看见和选择使用示例 14-4 中的内部结构,或者可以使用示例 14-5 中更为方便的结构,如示例 14-6 所示:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch14-more-about-cargo/listing-14-06/src/main.rs:here}}
@ -162,7 +162,7 @@ $ cargo login abcdefghijklmnopqrstuvwxyz012345
首先 crate 需要一个唯一的名称。虽然在本地开发 crate 时,可以使用任何你喜欢的名称。不过 [crates.io](https://crates.io)<!-- ignore --> 上的 crate 名称遵守先到先得的分配原则。一旦某个 crate 名称被使用,其他人就不能再发布这个名称的 crate 了。请在网站上搜索你希望使用的名称来找出它是否已被使用。如果没有,修改 _Cargo.toml_`[package]` 里的名称为你希望用于发布的名称,像这样:
<span class="filename">文件名: Cargo.toml</span>
<span class="filename">文件名Cargo.toml</span>
```toml
[package]
@ -189,7 +189,7 @@ Caused by:
[spdx]: http://spdx.org/licenses/
<span class="filename">文件名: Cargo.toml</span>
<span class="filename">文件名Cargo.toml</span>
```toml
[package]
@ -203,7 +203,7 @@ license = "MIT"
那么,有了唯一的名称、版本号、由 `cargo new` 新建项目时增加的作者信息、描述和所选择的 license已经准备好发布的项目的 _Cargo.toml_ 文件可能看起来像这样:
<span class="filename">文件名: Cargo.toml</span>
<span class="filename">文件名Cargo.toml</span>
```toml
[package]

View File

@ -17,7 +17,7 @@ $ cd add
接着在 *add* 目录中,创建 *Cargo.toml* 文件。这个 *Cargo.toml* 文件配置了整个工作空间。它不会包含 `[package]` 或其他我们在 *Cargo.toml* 中见过的元信息。相反,它以 `[workspace]` 部分作为开始,并通过指定 *adder* 的路径来为工作空间增加成员,如下会加入二进制 crate
<span class="filename">文件名: Cargo.toml</span>
<span class="filename">文件名Cargo.toml</span>
```toml
{{#include ../listings/ch14-more-about-cargo/no-listing-01-workspace-with-adder-crate/add/Cargo.toml}}
@ -48,7 +48,7 @@ $ cargo new adder
接下来,让我们在工作空间中指定另一个成员 crate。这个 crate 位于 *add_one* 目录中,所以修改顶级 *Cargo.toml* 为也包含 *add_one* 路径:
<span class="filename">文件名: Cargo.toml</span>
<span class="filename">文件名Cargo.toml</span>
```toml
{{#include ../listings/ch14-more-about-cargo/no-listing-02-workspace-with-two-crates/add/Cargo.toml}}
@ -79,7 +79,7 @@ $ cargo new add_one --lib
*add_one/src/lib.rs* 文件中,增加一个 `add_one` 函数:
<span class="filename">文件名: add_one/src/lib.rs</span>
<span class="filename">文件名add_one/src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch14-more-about-cargo/no-listing-02-workspace-with-two-crates/add/add_one/src/lib.rs}}
@ -87,17 +87,17 @@ $ cargo new add_one --lib
现在工作空间中有了一个库 crate`adder` 依赖库 crate `add_one`。首先需要在 *adder/Cargo.toml* 文件中增加 `add_one` 作为路径依赖:
<span class="filename">文件名: adder/Cargo.toml</span>
<span class="filename">文件名adder/Cargo.toml</span>
```toml
{{#include ../listings/ch14-more-about-cargo/no-listing-02-workspace-with-two-crates/add/adder/Cargo.toml:6:7}}
```
cargo并不假定工作空间中的Crates会相互依赖所以需要明确表明工作空间中 crate 的依赖关系。
cargo 并不假定工作空间中的 Crates 会相互依赖,所以需要明确表明工作空间中 crate 的依赖关系。
接下来,在 `adder` crate 中使用 `add_one` crate 的函数 `add_one`。打开 *adder/src/main.rs* 在顶部增加一行 `use` 将新 `add_one` 库 crate 引入作用域。接着修改 `main` 函数来调用 `add_one` 函数,如示例 14-7 所示。
<span class="filename">文件名: adder/src/main.rs</span>
<span class="filename">文件名adder/src/main.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch14-more-about-cargo/listing-14-07/add/adder/src/main.rs}}
@ -130,7 +130,7 @@ Hello, world! 10 plus one is 11!
还需注意的是工作空间只在根目录有一个 *Cargo.lock*,而不是在每一个 crate 目录都有 *Cargo.lock*。这确保了所有的 crate 都使用完全相同版本的依赖。如果在 *Cargo.toml**add_one/Cargo.toml* 中都增加 `rand` crate则 Cargo 会将其都解析为同一版本并记录到唯一的 *Cargo.lock* 中。使得工作空间中的所有 crate 都使用相同的依赖意味着其中的 crate 都是相互兼容的。让我们在 *add_one/Cargo.toml* 中的 `[dependencies]` 部分增加 `rand` crate 以便能够在 `add_one` crate 中使用 `rand` crate
<span class="filename">文件名: add_one/Cargo.toml</span>
<span class="filename">文件名add_one/Cargo.toml</span>
```toml
{{#include ../listings/ch14-more-about-cargo/no-listing-03-workspace-with-external-dependency/add/add_one/Cargo.toml:6:7}}
@ -178,7 +178,7 @@ error[E0432]: unresolved import `rand`
作为另一个提升,让我们为 `add_one` crate 中的 `add_one::add_one` 函数增加一个测试:
<span class="filename">文件名: add_one/src/lib.rs</span>
<span class="filename">文件名add_one/src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch14-more-about-cargo/no-listing-04-workspace-with-tests/add/add_one/src/lib.rs}}

View File

@ -3,7 +3,7 @@
> [ch15-01-box.md](https://github.com/rust-lang/book/blob/main/src/ch15-01-box.md) <br>
> commit 359895c6b2e440275a663ee1a3c17e6a94fdc62b
最简单直接的智能指针是 _box_,其类型是 `Box<T>` box 允许你将一个值放在堆上而不是栈上。留在栈上的则是指向堆数据的指针。如果你想回顾一下栈与堆的区别请参考第四章。
最简单直接的智能指针是 _box_,其类型是 `Box<T>`。box 允许你将一个值放在堆上而不是栈上。留在栈上的则是指向堆数据的指针。如果你想回顾一下栈与堆的区别请参考第四章。
除了数据被储存在堆上而不是栈上之外box 没有性能损失。不过也没有很多额外的功能。它们多用于如下场景:
@ -19,7 +19,7 @@
示例 15-1 展示了如何使用 box 在堆上储存一个 `i32`
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-01/src/main.rs}}
@ -49,7 +49,7 @@ cons list 的每一项都包含两个元素:当前项的值和下一项。其
示例 15-2 包含一个 cons list 的枚举定义。注意这还不能编译因为这个类型没有已知的大小,之后我们会展示:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-02/src/main.rs:here}}
@ -61,7 +61,7 @@ cons list 的每一项都包含两个元素:当前项的值和下一项。其
使用这个 cons list 来储存列表 `1, 2, 3` 将看起来如示例 15-3 所示:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-03/src/main.rs:here}}
@ -114,7 +114,7 @@ help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to make `List` repre
我们可以修改示例 15-2 中 `List` 枚举的定义和示例 15-3 中对 `List` 的应用,如示例 15-65 所示,这是可以编译的:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-05/src/main.rs}}
@ -122,7 +122,7 @@ help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to make `List` repre
<span class="caption">示例 15-5为了拥有已知大小而使用 `Box<T>``List` 定义</span>
`Cons` 成员将会需要一个 `i32` 的大小加上储存 box 指针数据的空间。`Nil` 成员不储存值,所以它比 `Cons` 成员需要更少的空间。现在我们知道了任何 `List` 值最多需要一个 `i32` 加上 box 指针数据的大小。通过使用 box ,打破了这无限递归的连锁,这样编译器就能够计算出储存 `List` 值需要的大小了。图 15-2 展示了现在 `Cons` 成员看起来像什么:
`Cons` 成员将会需要一个 `i32` 的大小加上储存 box 指针数据的空间。`Nil` 成员不储存值,所以它比 `Cons` 成员需要更少的空间。现在我们知道了任何 `List` 值最多需要一个 `i32` 加上 box 指针数据的大小。通过使用 box打破了这无限递归的连锁这样编译器就能够计算出储存 `List` 值需要的大小了。图 15-2 展示了现在 `Cons` 成员看起来像什么:
<img alt="A finite Cons list" src="img/trpl15-02.svg" class="center" />

View File

@ -13,7 +13,7 @@
常规引用是一个指针类型,一种理解指针的方式是将其看成指向储存在其他某处值的箭头。在示例 15-6 中,创建了一个 `i32` 值的引用,接着使用解引用运算符来跟踪所引用的数据:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-06/src/main.rs}}
@ -35,7 +35,7 @@
可以使用 `Box<T>` 代替引用来重写示例 15-6 中的代码,解引用运算符也一样能工作,如示例 15-7 所示:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-07/src/main.rs}}
@ -51,7 +51,7 @@
从根本上说,`Box<T>` 被定义为包含一个元素的元组结构体,所以示例 15-8 以相同的方式定义了 `MyBox<T>` 类型。我们还定义了 `new` 函数来对应定义于 `Box<T>``new` 函数:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-08/src/main.rs:here}}
@ -63,7 +63,7 @@
尝试将示例 15-7 中的代码加入示例 15-8 中并修改 `main` 使用我们定义的 `MyBox<T>` 类型代替 `Box<T>`。示例 15-9 中的代码不能编译,因为 Rust 不知道如何解引用 `MyBox`
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-09/src/main.rs:here}}
@ -83,7 +83,7 @@
如第十章 [“为类型实现 trait”][impl-trait] 部分所讨论的,为了实现 trait需要提供 trait 所需的方法实现。`Deref` trait由标准库提供要求实现名为 `deref` 的方法,其借用 `self` 并返回一个内部数据的引用。示例 15-10 包含定义于 `MyBox` 之上的 `Deref` 实现:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-10/src/main.rs:here}}
@ -117,7 +117,7 @@ Deref 强制转换的加入使得 Rust 程序员编写函数和方法调用时
作为展示 Deref 强制转换的实例,让我们使用示例 15-8 中定义的 `MyBox<T>`,以及示例 15-10 中增加的 `Deref` 实现。示例 15-11 展示了一个有着字符串 slice 参数的函数定义:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-11/src/main.rs:here}}
@ -127,7 +127,7 @@ Deref 强制转换的加入使得 Rust 程序员编写函数和方法调用时
可以使用字符串 slice 作为参数调用 `hello` 函数,比如 `hello("Rust");`。Deref 强制转换使得用 `MyBox<String>` 类型值的引用调用 `hello` 成为可能,如示例 15-12 所示:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-12/src/main.rs:here}}
@ -139,7 +139,7 @@ Deref 强制转换的加入使得 Rust 程序员编写函数和方法调用时
如果 Rust 没有实现 Deref 强制转换,为了使用 `&MyBox<String>` 类型的值调用 `hello`,则不得不编写示例 15-13 中的代码来代替示例 15-12
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-13/src/main.rs:here}}

View File

@ -11,7 +11,7 @@
示例 15-14 展示了唯一定制功能就是当其实例离开作用域时,打印出 `Dropping CustomSmartPointer!` 的结构体 `CustomSmartPointer`。这会演示 Rust 何时运行 `drop` 函数:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-14/src/main.rs}}
@ -37,7 +37,7 @@
如果我们像是示例 15-14 那样尝试调用 `Drop` trait 的 `drop` 方法,就会得到像示例 15-15 那样的编译错误:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-15/src/main.rs:here}}
@ -59,7 +59,7 @@ Rust 不允许我们显式调用 `drop` 因为 Rust 仍然会在 `main` 的结
`std::mem::drop` 函数不同于 `Drop` trait 中的 `drop` 方法。可以通过传递希望提早强制丢弃的值作为参数。`std::mem::drop` 位于 prelude所以我们可以修改示例 15-15 中的 `main` 来调用 `drop` 函数。如示例 15-16 所示:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-16/src/main.rs:here}}

View File

@ -19,13 +19,13 @@
<img alt="Two lists that share ownership of a third list" src="img/trpl15-03.svg" class="center" />
<span class="caption">图 15-3: 两个列表, `b``c`, 共享第三个列表 `a` 的所有权</span>
<span class="caption">图 15-3: 两个列表`b``c`, 共享第三个列表 `a` 的所有权</span>
列表 `a` 包含 5 之后是 10之后是另两个列表`b` 从 3 开始而 `c` 从 4 开始。`b` 和 `c` 会接上包含 5 和 10 的列表 `a`。换句话说,这两个列表会尝试共享第一个列表所包含的 5 和 10。
尝试使用 `Box<T>` 定义的 `List` 实现并不能工作,如示例 15-17 所示:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-17/src/main.rs}}
@ -45,7 +45,7 @@
相反,我们修改 `List` 的定义为使用 `Rc<T>` 代替 `Box<T>`,如列表 15-18 所示。现在每一个 `Cons` 变量都包含一个值和一个指向 `List``Rc<T>`。当创建 `b` 时,不同于获取 `a` 的所有权,这里会克隆 `a` 所包含的 `Rc<List>`,这会将引用计数从 1 增加到 2 并允许 `a``b` 共享 `Rc<List>` 中数据的所有权。创建 `c` 时也会克隆 `a`,这会将引用计数从 2 增加为 3。每次调用 `Rc::clone``Rc<List>` 中数据的引用计数都会增加,直到有零个引用之前其数据都不会被清理。
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-18/src/main.rs}}
@ -63,7 +63,7 @@
在示例 15-19 中,修改了 `main` 以便将列表 `c` 置于内部作用域中,这样就可以观察当 `c` 离开作用域时引用计数如何变化。
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-19/src/main.rs:here}}
@ -79,7 +79,7 @@
{{#include ../listings/ch15-smart-pointers/listing-15-19/output.txt}}
```
我们能够看到 `a``Rc<List>` 的初始引用计数为1接着每次调用 `clone`计数会增加1。当 `c` 离开作用域时计数减1。不必像调用 `Rc::clone` 增加引用计数那样调用一个函数来减少计数;`Drop` trait 的实现当 `Rc<T>` 值离开作用域时自动减少引用计数。
我们能够看到 `a``Rc<List>` 的初始引用计数为 1接着每次调用 `clone`,计数会增加 1。当 `c` 离开作用域时,计数减 1。不必像调用 `Rc::clone` 增加引用计数那样调用一个函数来减少计数;`Drop` trait 的实现当 `Rc<T>` 值离开作用域时自动减少引用计数。
从这个例子我们所不能看到的是,在 `main` 的结尾当 `b` 然后是 `a` 离开作用域时,此处计数会是 0同时 `Rc<List>` 被完全清理。使用 `Rc<T>` 允许一个值有多个所有者,引用计数则确保只要任何所有者依然存在其值也保持有效。

View File

@ -60,7 +60,7 @@
该库只提供记录与最大值的差距,以及何种情况发送什么消息的功能。使用此库的程序则期望提供实际发送消息的机制:程序可以选择记录一条消息、发送 email、发送短信等等。库本身无需知道这些细节只需实现其提供的 `Messenger` trait 即可。示例 15-20 展示了库代码:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-20/src/lib.rs}}
@ -72,7 +72,7 @@
我们所需的 mock 对象是,调用 `send` 并不实际发送 email 或消息,而是只记录信息被通知要发送了。可以新建一个 mock 对象实例,用其创建 `LimitTracker`,调用 `LimitTracker``set_value` 方法,然后检查 mock 对象是否有我们期望的消息。示例 15-21 展示了一个如此尝试的 mock 对象实现,不过借用检查器并不允许:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-21/src/lib.rs:here}}
@ -94,7 +94,7 @@
这正是内部可变性的用武之地!我们将通过 `RefCell` 来储存 `sent_messages`,然后 `send` 将能够修改 `sent_messages` 并储存消息。示例 15-22 展示了代码:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-22/src/lib.rs:here}}
@ -118,7 +118,7 @@
如果我们尝试违反这些规则,相比引用时的编译时错误,`RefCell<T>` 的实现会在运行时出现 panic。示例 15-23 展示了对示例 15-22 中 `send` 实现的修改,这里我们故意尝试在相同作用域创建两个可变借用以便演示 `RefCell<T>` 不允许我们在运行时这么做:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,ignore,panics
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-23/src/lib.rs:here}}
@ -142,7 +142,7 @@
例如,回忆示例 15-18 的 cons list 的例子中使用 `Rc<T>` 使得多个列表共享另一个列表的所有权。因为 `Rc<T>` 只存放不可变值,所以一旦创建了这些列表值后就不能修改。让我们加入 `RefCell<T>` 来获得修改列表中值的能力。示例 15-24 展示了通过在 `Cons` 定义中使用 `RefCell<T>`,我们就允许修改所有列表中的值了:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-24/src/main.rs}}

View File

@ -3,13 +3,13 @@
> [ch15-06-reference-cycles.md](https://github.com/rust-lang/book/blob/main/src/ch15-06-reference-cycles.md) <br>
> commit bd27a8b72336610c9a200f0ca932ffc8b6fb5ee1
Rust 的内存安全性保证使其难以意外地制造永远也不会被清理的内存(被称为 **内存泄漏**_memory leak_但并不是不可能。与在编译时拒绝数据竞争不同 Rust 并不保证完全地避免内存泄漏,这意味着内存泄漏在 Rust 被认为是内存安全的。这一点可以通过 `Rc<T>``RefCell<T>` 看出:创建引用循环的可能性是存在的。这会造成内存泄漏,因为每一项的引用计数永远也到不了 0其值也永远不会被丢弃。
Rust 的内存安全性保证使其难以意外地制造永远也不会被清理的内存(被称为 **内存泄漏**_memory leak_但并不是不可能。与在编译时拒绝数据竞争不同Rust 并不保证完全地避免内存泄漏,这意味着内存泄漏在 Rust 被认为是内存安全的。这一点可以通过 `Rc<T>``RefCell<T>` 看出:创建引用循环的可能性是存在的。这会造成内存泄漏,因为每一项的引用计数永远也到不了 0其值也永远不会被丢弃。
### 制造引用循环
让我们看看引用循环是如何发生的以及如何避免它。以示例 15-25 中的 `List` 枚举和 `tail` 方法的定义开始:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-25/src/main.rs}}
@ -21,7 +21,7 @@ Rust 的内存安全性保证使其难以意外地制造永远也不会被清理
在示例 15-26 中增加了一个 `main` 函数,其使用了示例 15-25 中的定义。这些代码在 `a` 中创建了一个列表,一个指向 `a` 中列表的 `b` 列表,接着修改 `a` 中的列表指向 `b` 中的列表,这会创建一个引用循环。在这个过程的多个位置有 `println!` 语句展示引用计数。
<span class="filename">文件: src/main.rs</span>
<span class="filename">文件src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-26/src/main.rs:here}}
@ -67,7 +67,7 @@ Rust 的内存安全性保证使其难以意外地制造永远也不会被清理
在最开始,我们将会构建一个带有子节点的树。让我们创建一个用于存放其拥有所有权的 `i32` 值和其子节点引用的 `Node`
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-27/src/main.rs:here}}
@ -77,7 +77,7 @@ Rust 的内存安全性保证使其难以意外地制造永远也不会被清理
接下来,使用此结构体定义来创建一个叫做 `leaf` 的带有值 3 且没有子节点的 `Node` 实例,和另一个带有值 5 并以 `leaf` 作为子节点的实例 `branch`,如示例 15-27 所示:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-27/src/main.rs:there}}
@ -95,7 +95,7 @@ Rust 的内存安全性保证使其难以意外地制造永远也不会被清理
所以 `parent` 使用 `Weak<T>` 类型而不是 `Rc<T>`,具体来说是 `RefCell<Weak<Node>>`。现在 `Node` 结构体定义看起来像这样:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-28/src/main.rs:here}}
@ -103,7 +103,7 @@ Rust 的内存安全性保证使其难以意外地制造永远也不会被清理
这样,一个节点就能够引用其父节点,但不拥有其父节点。在示例 15-28 中,我们更新 `main` 来使用新定义以便 `leaf` 节点可以通过 `branch` 引用其父节点:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-28/src/main.rs:there}}
@ -135,7 +135,7 @@ children: RefCell { value: [] } }] } })
让我们通过创建了一个新的内部作用域并将 `branch` 的创建放入其中,来观察 `Rc<Node>` 实例的 `strong_count``weak_count` 值的变化。这会展示当 `branch` 创建和离开作用域被丢弃时会发生什么。这些修改如示例 15-29 所示:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-29/src/main.rs:here}}

View File

@ -19,7 +19,7 @@ Rust 尝试减轻使用线程的负面影响。不过在多线程上下文中编
为了创建一个新线程,需要调用 `thread::spawn` 函数并传递一个闭包(第十三章学习了闭包),并在其中包含希望在新线程运行的代码。示例 16-1 中的例子在主线程打印了一些文本而另一些文本则由新线程打印:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch16-fearless-concurrency/listing-16-01/src/main.rs}}
@ -41,7 +41,7 @@ hi number 4 from the spawned thread!
hi number 5 from the spawned thread!
```
`thread::sleep` 调用强制线程停止执行一小段时间,这会允许其他不同的线程运行。这些线程可能会轮流运行,不过并不保证如此:这依赖操作系统如何调度线程。在这里,主线程首先打印,即便新创建线程的打印语句位于程序的开头,甚至即便我们告诉新建的线程打印直到 `i` 等于 9 ,它在主线程结束之前也只打印到了 5。
`thread::sleep` 调用强制线程停止执行一小段时间,这会允许其他不同的线程运行。这些线程可能会轮流运行,不过并不保证如此:这依赖操作系统如何调度线程。在这里,主线程首先打印,即便新创建线程的打印语句位于程序的开头,甚至即便我们告诉新建的线程打印直到 `i` 等于 9它在主线程结束之前也只打印到了 5。
如果运行代码只看到了主线程的输出,或没有出现重叠打印的现象,尝试增大区间 (变量 `i` 的范围) 来增加操作系统切换线程的机会。
@ -51,7 +51,7 @@ hi number 5 from the spawned thread!
可以通过将 `thread::spawn` 的返回值储存在变量中来修复新建线程部分没有执行或者完全没有执行的问题。`thread::spawn` 的返回值类型是 `JoinHandle`。`JoinHandle` 是一个拥有所有权的值,当对其调用 `join` 方法时,它会等待其线程结束。示例 16-2 展示了如何使用示例 16-1 中创建的线程的 `JoinHandle` 并调用 `join` 来确保新建线程在 `main` 退出前结束运行:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch16-fearless-concurrency/listing-16-02/src/main.rs}}
@ -59,7 +59,7 @@ hi number 5 from the spawned thread!
<span class="caption">示例 16-2: 从 `thread::spawn` 保存一个 `JoinHandle` 以确保该线程能够运行至结束</span>
通过调用 handle 的 `join` 会阻塞当前线程直到 handle 所代表的线程结束。**阻塞**_Blocking_ 线程意味着阻止该线程执行工作或退出。因为我们将 `join` 调用放在了主线程的 `for` 循环之后,运行示例 16-2 应该会产生类似这样的输出:
通过调用 handle 的 `join` 会阻塞当前线程直到 handle 所代表的线程结束。**阻塞**_Blocking_线程意味着阻止该线程执行工作或退出。因为我们将 `join` 调用放在了主线程的 `for` 循环之后,运行示例 16-2 应该会产生类似这样的输出:
```text
hi number 1 from the main thread!
@ -81,7 +81,7 @@ hi number 9 from the spawned thread!
不过让我们看看将 `handle.join()` 移动到 `main``for` 循环之前会发生什么,如下:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch16-fearless-concurrency/no-listing-01-join-too-early/src/main.rs}}
@ -115,7 +115,7 @@ hi number 4 from the main thread!
注意示例 16-1 中传递给 `thread::spawn` 的闭包并没有任何参数:并没有在新建线程代码中使用任何主线程的数据。为了在新建线程中使用来自于主线程的数据,需要新建线程的闭包获取它需要的值。示例 16-3 展示了一个尝试在主线程中创建一个 vector 并用于新建线程的例子,不过这么写还不能工作,如下所示:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch16-fearless-concurrency/listing-16-03/src/main.rs}}
@ -133,7 +133,7 @@ Rust 会 **推断** 如何捕获 `v`,因为 `println!` 只需要 `v` 的引用
示例 16-4 展示了一个 `v` 的引用很有可能不再有效的场景:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch16-fearless-concurrency/listing-16-04/src/main.rs}}
@ -154,7 +154,7 @@ help: to force the closure to take ownership of `v` (and any other referenced va
通过在闭包之前增加 `move` 关键字,我们强制闭包获取其使用的值的所有权,而不是任由 Rust 推断它应该借用值。示例 16-5 中展示的对示例 16-3 代码的修改,可以按照我们的预期编译并运行:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch16-fearless-concurrency/listing-16-05/src/main.rs}}

View File

@ -13,7 +13,7 @@ Rust 中一个实现消息传递并发的主要工具是 **信道**_channel_
首先,在示例 16-6 中,创建了一个信道但没有做任何事。注意这还不能编译,因为 Rust 不知道我们想要在信道中发送什么类型:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch16-fearless-concurrency/listing-16-06/src/main.rs}}
@ -27,7 +27,7 @@ Rust 中一个实现消息传递并发的主要工具是 **信道**_channel_
让我们将发送端移动到一个新建线程中并发送一个字符串,这样新建线程就可以和主线程通讯了,如示例 16-7 所示。这类似于在河的上游扔下一只橡皮鸭或从一个线程向另一个线程发送聊天信息:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch16-fearless-concurrency/listing-16-07/src/main.rs}}
@ -41,7 +41,7 @@ Rust 中一个实现消息传递并发的主要工具是 **信道**_channel_
在示例 16-8 中,我们在主线程中从信道的接收端获取值。这类似于在河的下游捞起橡皮鸭或接收聊天信息:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch16-fearless-concurrency/listing-16-08/src/main.rs}}
@ -67,7 +67,7 @@ Got: hi
所有权规则在消息传递中扮演了重要角色,其有助于我们编写安全的并发代码。防止并发编程中的错误是在 Rust 程序中考虑所有权的一大优势。现在让我们做一个试验来看看信道与所有权如何一同协作以避免产生问题:我们将尝试在新建线程中的信道中发送完 `val`**之后** 再使用它。尝试编译示例 16-9 中的代码并看看为何这是不允许的:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch16-fearless-concurrency/listing-16-09/src/main.rs}}
@ -87,7 +87,7 @@ Got: hi
示例 16-8 中的代码可以编译和运行,不过它并没有明确的告诉我们两个独立的线程通过信道相互通讯。示例 16-10 则有一些改进会证明示例 16-8 中的代码是并发执行的:新建线程现在会发送多个消息并在每个消息之间暂停一秒钟。
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch16-fearless-concurrency/listing-16-10/src/main.rs}}
@ -114,7 +114,7 @@ Got: thread
之前我们提到了`mpsc`是 _multiple producer, single consumer_ 的缩写。可以运用 `mpsc` 来扩展示例 16-10 中的代码来创建向同一接收者发送值的多个线程。这可以通过克隆信道的发送端来做到,如示例 16-11 所示:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch16-fearless-concurrency/listing-16-11/src/main.rs:here}}

View File

@ -28,7 +28,7 @@
作为展示如何使用互斥器的例子,让我们从在单线程上下文使用互斥器开始,如示例 16-12 所示:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch16-fearless-concurrency/listing-16-12/src/main.rs}}
@ -50,7 +50,7 @@
现在让我们尝试使用 `Mutex<T>` 在多个线程间共享值。我们将启动十个线程,并在各个线程中对同一个计数器值加一,这样计数器将从 0 变为 10。示例 16-13 中的例子会出现编译错误,而我们将通过这些错误来学习如何使用 `Mutex<T>`,以及 Rust 又是如何帮助我们正确使用的。
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch16-fearless-concurrency/listing-16-13/src/main.rs}}
@ -74,7 +74,7 @@
在第十五章中,通过使用智能指针 `Rc<T>` 来创建引用计数的值,以便拥有多所有者。让我们在这也这么做看看会发生什么。将示例 16-14 中的 `Mutex<T>` 封装进 `Rc<T>` 中并在将所有权移入线程之前克隆了 `Rc<T>`
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch16-fearless-concurrency/listing-16-14/src/main.rs}}
@ -100,7 +100,7 @@
回到之前的例子:`Arc<T>` 和 `Rc<T>` 有着相同的 API所以修改程序中的 `use` 行和 `new` 调用。示例 16-15 中的代码最终可以编译和运行:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch16-fearless-concurrency/listing-16-15/src/main.rs}}
@ -120,7 +120,7 @@ Result: 10
你可能注意到了,因为 `counter` 是不可变的,不过可以获取其内部值的可变引用;这意味着 `Mutex<T>` 提供了内部可变性,就像 `Cell` 系列类型那样。正如第十五章中使用 `RefCell<T>` 可以改变 `Rc<T>` 中的内容那样,同样的可以使用 `Mutex<T>` 来改变 `Arc<T>` 中的内容。
另一个值得注意的细节是 Rust 不能避免使用 `Mutex<T>` 的全部逻辑错误。回忆一下第十五章使用 `Rc<T>` 就有造成引用循环的风险,这时两个 `Rc<T>` 值相互引用,造成内存泄漏。同理,`Mutex<T>` 也有造成 **死锁**_deadlock_ 的风险。这发生于当一个操作需要锁住两个资源而两个线程各持一个锁,这会造成它们永远相互等待。如果你对这个主题感兴趣,尝试编写一个带有死锁的 Rust 程序,接着研究任何其他语言中使用互斥器的死锁规避策略并尝试在 Rust 中实现他们。标准库中 `Mutex<T>``MutexGuard` 的 API 文档会提供有用的信息。
另一个值得注意的细节是 Rust 不能避免使用 `Mutex<T>` 的全部逻辑错误。回忆一下第十五章使用 `Rc<T>` 就有造成引用循环的风险,这时两个 `Rc<T>` 值相互引用,造成内存泄漏。同理,`Mutex<T>` 也有造成 **死锁**_deadlock_的风险。这发生于当一个操作需要锁住两个资源而两个线程各持一个锁这会造成它们永远相互等待。如果你对这个主题感兴趣尝试编写一个带有死锁的 Rust 程序,接着研究任何其他语言中使用互斥器的死锁规避策略并尝试在 Rust 中实现他们。标准库中 `Mutex<T>``MutexGuard` 的 API 文档会提供有用的信息。
接下来,为了丰富本章的内容,让我们讨论一下 `Send``Sync` trait 以及如何对自定义类型使用他们。

View File

@ -24,7 +24,7 @@
就像我们在第七章讨论的那样:可以使用 `pub` 关键字来决定模块、类型、函数和方法是公有的,而默认情况下其他一切都是私有的。比如,我们可以定义一个包含一个 `i32` 类型 vector 的结构体 `AveragedCollection `。结构体也可以有一个字段,该字段保存了 vector 中所有值的平均值。这样,希望知道结构体中的 vector 的平均值的人可以随时获取它,而无需自己计算。换句话说,`AveragedCollection` 会为我们缓存平均值结果。示例 17-1 有 `AveragedCollection` 结构体的定义:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch17-oop/listing-17-01/src/lib.rs}}
@ -34,7 +34,7 @@
注意,结构体自身被标记为 `pub`,这样其他代码就可以使用这个结构体,但是在结构体内部的字段仍然是私有的。这是非常重要的,因为我们希望保证变量被增加到列表或者被从列表删除时,也会同时更新平均值。可以通过在结构体上实现 `add`、`remove` 和 `average` 方法来做到这一点,如示例 17-2 所示:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch17-oop/listing-17-02/src/lib.rs:here}}

View File

@ -6,7 +6,7 @@
在第八章中,我们谈到了 vector 只能存储同种类型元素的局限。示例 8-10 中提供了一个定义 `SpreadsheetCell` 枚举来储存整型,浮点型和文本成员的替代方案。这意味着可以在每个单元中储存不同类型的数据,并仍能拥有一个代表一排单元的 vector。这在当编译代码时就知道希望可以交替使用的类型为固定集合的情况下是完全可行的。
然而有时我们希望库用户在特定情况下能够扩展有效的类型集合。为了展示如何实现这一点这里将创建一个图形用户接口Graphical User Interface GUI工具的例子它通过遍历列表并调用每一个项目的 `draw` 方法来将其绘制到屏幕上 —— 此乃一个 GUI 工具的常见技术。我们将要创建一个叫做 `gui` 的库 crate它含一个 GUI 库的结构。这个 GUI 库包含一些可供开发者使用的类型,比如 `Button``TextField`。在此之上,`gui` 的用户希望创建自定义的可以绘制于屏幕上的类型:比如,一个程序员可能会增加 `Image`,另一个可能会增加 `SelectBox`
然而有时我们希望库用户在特定情况下能够扩展有效的类型集合。为了展示如何实现这一点这里将创建一个图形用户接口Graphical User InterfaceGUI工具的例子它通过遍历列表并调用每一个项目的 `draw` 方法来将其绘制到屏幕上 —— 此乃一个 GUI 工具的常见技术。我们将要创建一个叫做 `gui` 的库 crate它含一个 GUI 库的结构。这个 GUI 库包含一些可供开发者使用的类型,比如 `Button``TextField`。在此之上,`gui` 的用户希望创建自定义的可以绘制于屏幕上的类型:比如,一个程序员可能会增加 `Image`,另一个可能会增加 `SelectBox`
这个例子中并不会实现一个功能完善的 GUI 库,不过会展示其中各个部分是如何结合在一起的。编写库的时候,我们不可能知晓并定义所有其他程序员希望创建的类型。我们所知晓的是 `gui` 需要记录一系列不同类型的值,并需要能够对其中每一个值调用 `draw` 方法。这里无需知道调用 `draw` 方法时具体会发生什么,只要该值会有那个方法可供我们调用。
@ -14,13 +14,13 @@
### 定义通用行为的 trait
为了实现 `gui` 所期望的行为,让我们定义一个 `Draw` trait其中包含名为 `draw` 的方法。接着可以定义一个存放 **trait 对象***trait object* 的 vector。trait 对象指向一个实现了我们指定 trait 的类型的实例以及一个用于在运行时查找该类型的trait方法的表。我们通过指定某种指针来创建 trait 对象,例如 `&` 引用或 `Box<T>` 智能指针,还有 `dyn` keyword 以及指定相关的 trait第十九章 [“动态大小类型和 `Sized` trait”][dynamically-sized] 部分会介绍 trait 对象必须使用指针的原因)。我们可以使用 trait 对象代替泛型或具体类型。任何使用 trait 对象的位置Rust 的类型系统会在编译时确保任何在此上下文中使用的值会实现其 trait 对象的 trait。如此便无需在编译时就知晓所有可能的类型。
为了实现 `gui` 所期望的行为,让我们定义一个 `Draw` trait其中包含名为 `draw` 的方法。接着可以定义一个存放 **trait 对象***trait object*)的 vector。trait 对象指向一个实现了我们指定 trait 的类型的实例,以及一个用于在运行时查找该类型的 trait 方法的表。我们通过指定某种指针来创建 trait 对象,例如 `&` 引用或 `Box<T>` 智能指针,还有 `dyn` keyword以及指定相关的 trait第十九章 [“动态大小类型和 `Sized` trait”][dynamically-sized] 部分会介绍 trait 对象必须使用指针的原因)。我们可以使用 trait 对象代替泛型或具体类型。任何使用 trait 对象的位置Rust 的类型系统会在编译时确保任何在此上下文中使用的值会实现其 trait 对象的 trait。如此便无需在编译时就知晓所有可能的类型。
之前提到过Rust 刻意不将结构体与枚举称为 “对象”,以便与其他语言中的对象相区别。在结构体或枚举中,结构体字段中的数据和 `impl` 块中的行为是分开的不同于其他语言中将数据和行为组合进一个称为对象的概念中。trait 对象将数据和行为两者相结合,从这种意义上说 **则** 其更类似其他语言中的对象。不过 trait 对象不同于传统的对象,因为不能向 trait 对象增加数据。trait 对象并不像其他语言中的对象那么通用trait 对象)具体的作用是允许对通用行为进行抽象。
示例 17-3 展示了如何定义一个带有 `draw` 方法的 trait `Draw`
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch17-oop/listing-17-03/src/lib.rs}}
@ -30,7 +30,7 @@
因为第十章已经讨论过如何定义 trait其语法看起来应该比较眼熟。接下来就是新内容了示例 17-4 定义了一个存放了名叫 `components` 的 vector 的结构体 `Screen`。这个 vector 的类型是 `Box<dyn Draw>`,此为一个 trait 对象:它是 `Box` 中任何实现了 `Draw` trait 的类型的替身。
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch17-oop/listing-17-04/src/lib.rs:here}}
@ -40,7 +40,7 @@
`Screen` 结构体上,我们将定义一个 `run` 方法,该方法会对其 `components` 上的每一个组件调用 `draw` 方法,如示例 17-5 所示:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch17-oop/listing-17-05/src/lib.rs:here}}
@ -50,7 +50,7 @@
这与定义使用了带有 trait bound 的泛型类型参数的结构体不同。泛型类型参数一次只能替代一个具体类型,而 trait 对象则允许在运行时替代多种具体类型。例如,可以定义 `Screen` 结构体来使用泛型和 trait bound如示例 17-6 所示:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch17-oop/listing-17-06/src/lib.rs:here}}
@ -66,7 +66,7 @@
现在来增加一些实现了 `Draw` trait 的类型。我们将提供 `Button` 类型。再一次重申,真正实现 GUI 库超出了本书的范畴,所以 `draw` 方法体中不会有任何有意义的实现。为了想象一下这个实现看起来像什么,一个 `Button` 结构体可能会拥有 `width`、`height` 和 `label` 字段,如示例 17-7 所示:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch17-oop/listing-17-07/src/lib.rs:here}}
@ -78,7 +78,7 @@
如果一些库的使用者决定实现一个包含 `width`、`height` 和 `options` 字段的结构体 `SelectBox`,并且也为其实现了 `Draw` trait如示例 17-8 所示:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch17-oop/listing-17-08/src/main.rs:here}}
@ -88,7 +88,7 @@
库使用者现在可以在他们的 `main` 函数中创建一个 `Screen` 实例。至此可以通过将 `SelectBox``Button` 放入 `Box<T>` 转变为 trait 对象来增加组件。接着可以调用 `Screen``run` 方法,它会调用每个组件的 `draw` 方法。示例 17-9 展示了这个实现:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch17-oop/listing-17-09/src/main.rs:here}}
@ -104,7 +104,7 @@
例如,示例 17-10 展示了当创建一个使用 `String` 做为其组件的 `Screen` 时发生的情况:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch17-oop/listing-17-10/src/main.rs}}
@ -126,9 +126,9 @@
当使用 trait 对象时Rust 必须使用动态分发。编译器无法知晓所有可能用于 trait 对象代码的类型所以它也不知道应该调用哪个类型的哪个方法实现。为此Rust 在运行时使用 trait 对象中的指针来知晓需要调用哪个方法。动态分发也阻止编译器有选择的内联方法代码,这会相应的禁用一些优化。尽管在编写示例 17-5 和可以支持示例 17-9 中的代码的过程中确实获得了额外的灵活性,但仍然需要权衡取舍。
### trait对象需要类型安全
### trait 对象需要类型安全
只有对象安全object-safe的trait可以实现为特征对象。这里有一些复杂的规则来实现trait的对象安全但在实践中只有两个相关的规则。如果一个 trait 中定义的所有方法都符合以下规则,则该 trait 是对象安全的:
只有对象安全object-safe trait 可以实现为特征对象。这里有一些复杂的规则来实现 trait 的对象安全,但在实践中,只有两个相关的规则。如果一个 trait 中定义的所有方法都符合以下规则,则该 trait 是对象安全的:
- 返回值不是 `Self`
- 没有泛型类型的参数
@ -145,7 +145,7 @@ pub trait Clone {
`String` 类型实现了 `Clone` trait当我们在 `String` 的实例对象上调用 `clone` 方法时,我们会得到一个 `String` 类型实例对象。相似地,如果我们调用 `Vec<T>` 实例对象上的 `clone` 方法,我们会得到一个 `Vec<T>` 类型的实例对象。`clone` 方法的标签需要知道哪个类型是 `Self` 类型,因为 `Self` 是它的返回类型。
当我们尝试编译一些违反 trait 对象的对象安全规则的代码时我们会收到编译器的提示。例如我们想实现17-4的 `Screen` 结构体来保存一个实现了 `Clone` trait 而不是 `Draw` trait 的类型,如下所示
当我们尝试编译一些违反 trait 对象的对象安全规则的代码时,我们会收到编译器的提示。例如,我们想实现 17-4 `Screen` 结构体来保存一个实现了 `Clone` trait 而不是 `Draw` trait 的类型,如下所示
```rust,ignore,does_not_compile
pub struct Screen {

View File

@ -19,7 +19,7 @@
示例 17-11 展示这个工作流的代码形式:这是一个我们将要在一个叫做 `blog` 的库 crate 中实现的 API 的示例。这段代码还不能编译,因为还未实现 `blog`
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch17-oop/listing-17-11/src/main.rs:all}}
@ -37,7 +37,7 @@
让我们开始实现这个库吧!我们知道需要一个公有 `Post` 结构体来存放一些文本,所以让我们从结构体的定义和一个创建 `Post` 实例的公有关联函数 `new` 开始,如示例 17-12 所示。还需定义一个私有 trait `State`。`Post` 将在私有字段 `state` 中存放一个 `Option<T>` 类型的 trait 对象 `Box<dyn State>`。稍后将会看到为何 `Option<T>` 是必须的。
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch17-oop/listing-17-12/src/lib.rs}}
@ -53,7 +53,7 @@
在示例 17-11 中,展示了我们希望能够调用一个叫做 `add_text` 的方法并向其传递一个 `&str` 来将文本增加到博文的内容中。选择实现为一个方法而不是将 `content` 字段暴露为 `pub` 。这意味着之后可以实现一个方法来控制 `content` 字段如何被读取。`add_text` 方法是非常直观的,让我们在示例 17-13 的 `impl Post` 块中增加一个实现:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch17-oop/listing-17-13/src/lib.rs:here}}
@ -67,7 +67,7 @@
即使调用 `add_text` 并向博文增加一些内容之后,我们仍然希望 `content` 方法返回一个空字符串 slice因为博文仍然处于草案状态如示例 17-11 的第 8 行所示。现在让我们使用能满足要求的最简单的方式来实现 `content` 方法:总是返回一个空字符串 slice。当实现了将博文状态改为发布的能力之后将改变这一做法。但是目前博文只能是草案状态这意味着其内容应该总是空的。示例 17-14 展示了这个占位符实现:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch17-oop/listing-17-14/src/lib.rs:here}}
@ -81,7 +81,7 @@
接下来需要增加请求审核博文的功能,这应当将其状态由 `Draft` 改为 `PendingReview`。示例 17-15 展示了这个代码:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch17-oop/listing-17-15/src/lib.rs:here}}
@ -107,7 +107,7 @@
`approve` 方法将与 `request_review` 方法类似:它会将 `state` 设置为审核通过时应处于的状态,如示例 17-16 所示。
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch17-oop/listing-17-16/src/lib.rs:here}}
@ -121,7 +121,7 @@
现在需要更新 `Post``content` 方法。我们希望 `content` 根据 `Post` 的当前状态返回值,所以需要 `Post` 代理一个定义于 `state` 上的 `content` 方法,如实例 17-17 所示:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch17-oop/listing-17-17/src/lib.rs:here}}
@ -137,7 +137,7 @@
接着我们就有了一个 `&Box<dyn State>`,当调用其 `content`Deref 强制转换会作用于 `&``Box` ,这样最终会调用实现了 `State` trait 的类型的 `content` 方法。这意味着需要为 `State` trait 定义增加 `content`,这也是放置根据所处状态返回什么内容的逻辑的地方,如示例 17-18 所示:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch17-oop/listing-17-18/src/lib.rs:here}}
@ -179,7 +179,7 @@
让我们考虑一下示例 17-11 中 `main` 的第一部分:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch17-oop/listing-17-11/src/main.rs:here}}
@ -187,7 +187,7 @@
我们仍然希望能够使用 `Post::new` 创建一个新的草案博文,并能够增加博文的内容。不过不同于存在一个草案博文时返回空字符串的 `content` 方法,我们将使草案博文完全没有 `content` 方法。这样如果尝试获取草案博文的内容,将会得到一个方法不存在的编译错误。这使得我们不可能在生产环境意外显示出草案博文的内容,因为这样的代码甚至就不能编译。示例 17-19 展示了 `Post` 结构体、`DraftPost` 结构体以及各自的方法的定义:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch17-oop/listing-17-19/src/lib.rs}}
@ -205,7 +205,7 @@
那么如何得到发布的博文呢?我们希望强制执行的规则是草案博文在可以发布之前必须被审核通过。等待审核状态的博文应该仍然不会显示任何内容。让我们通过增加另一个结构体 `PendingReviewPost` 来实现这个限制,在 `DraftPost` 上定义 `request_review` 方法来返回 `PendingReviewPost`,并在 `PendingReviewPost` 上定义 `approve` 方法来返回 `Post`,如示例 17-20 所示:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch17-oop/listing-17-20/src/lib.rs:here}}
@ -217,7 +217,7 @@
这也意味着不得不对 `main` 做出一些小的修改。因为 `request_review``approve` 返回新实例而不是修改被调用的结构体,所以我们需要增加更多的 `let post = ` 覆盖赋值来保存返回的实例。也不再能断言草案和等待审核的博文的内容为空字符串了,我们也不再需要他们:不能编译尝试使用这些状态下博文内容的代码。更新后的 `main` 的代码如示例 17-21 所示:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch17-oop/listing-17-21/src/main.rs}}
@ -235,7 +235,7 @@
阅读本章后,不管你是否认为 Rust 是一个面向对象语言,现在你都见识了 trait 对象是一个 Rust 中获取部分面向对象功能的方法。动态分发可以通过牺牲少量运行时性能来为你的代码提供一些灵活性。这些灵活性可以用来实现有助于代码可维护性的面向对象模式。Rust 也有像所有权这样不同于面向对象语言的功能。面向对象模式并不总是利用 Rust 优势的最好方式,但也是可用的选项。
接下来,让我们看看另一个提供了多样灵活性的 Rust 功能:模式。贯穿全书的模式, 我们已经和它们打过照面了,但并没有见识过它们的全部本领。让我们开始探索吧!
接下来,让我们看看另一个提供了多样灵活性的 Rust 功能:模式。贯穿全书的模式我们已经和它们打过照面了,但并没有见识过它们的全部本领。让我们开始探索吧!
[more-info-than-rustc]: ch09-03-to-panic-or-not-to-panic.html#当我们比编译器知道更多的情况
[macros]: ch19-06-macros.html#宏

View File

@ -30,7 +30,7 @@ match VALUE {
示例 18-1 中的代码展示了一系列针对不同条件的检查来决定背景颜色应该是什么。为了达到这个例子的目的,我们创建了硬编码值的变量,在真实程序中则可能由询问用户获得。
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch18-patterns-and-matching/listing-18-01/src/main.rs}}
@ -132,7 +132,7 @@ let PATTERN = EXPRESSION;
`x` 部分就是一个模式!类似于之前对 `let` 所做的,可以在函数参数中匹配元组。列表 18-7 将传递给函数的元组拆分为值:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch18-patterns-and-matching/listing-18-07/src/main.rs}}

View File

@ -48,6 +48,6 @@ Rust 会抱怨将不可反驳模式用于 `if let` 是没有意义的:
{{#include ../listings/ch18-patterns-and-matching/listing-18-10/output.txt}}
```
基于此,`match`匹配分支必须使用可反驳模式除了最后一个分支需要使用能匹配任何剩余值的不可反驳模式。Rust允许我们在只有一个匹配分支的`match`中使用不可反驳模式,不过这么做不是特别有用,并可以被更简单的 `let` 语句替代。
基于此,`match`匹配分支必须使用可反驳模式除了最后一个分支需要使用能匹配任何剩余值的不可反驳模式。Rust 允许我们在只有一个匹配分支的`match`中使用不可反驳模式,不过这么做不是特别有用,并可以被更简单的 `let` 语句替代。
目前我们已经讨论了所有可以使用模式的地方,以及可反驳模式与不可反驳模式的区别,下面让我们一起去把可以用来创建模式的语法过目一遍吧。

View File

@ -19,7 +19,7 @@
命名变量是匹配任何值的不可反驳模式,这在之前已经使用过数次。然而当其用于 `match` 表达式时情况会有些复杂。因为 `match` 会开始一个新作用域,`match` 表达式中作为模式的一部分声明的变量会覆盖 `match` 结构之外的同名变量,与所有变量一样。在示例 18-11 中,声明了一个值为 `Some(5)` 的变量 `x` 和一个值为 `10` 的变量 `y`。接着在值 `x` 上创建了一个 `match` 表达式。观察匹配分支中的模式和结尾的 `println!`,并在运行此代码或进一步阅读之前推断这段代码会打印什么。
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch18-patterns-and-matching/listing-18-11/src/main.rs:here}}
@ -75,7 +75,7 @@ Rust 知道 `c` 位于第一个模式的范围内,并会打印出 `early ASCII
示例 18-12 展示带有两个字段 `x``y` 的结构体 `Point`,可以通过带有模式的 `let` 语句将其分解:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch18-patterns-and-matching/listing-18-12/src/main.rs}}
@ -87,7 +87,7 @@ Rust 知道 `c` 位于第一个模式的范围内,并会打印出 `early ASCII
因为变量名匹配字段名是常见的,同时因为 `let Point { x: x, y: y } = p;` 包含了很多重复,所以对于匹配结构体字段的模式存在简写:只需列出结构体字段的名称,则模式创建的变量会有相同的名称。示例 18-13 展示了与示例 18-12 有着相同行为的代码,不过 `let` 模式创建的变量为 `x``y` 而不是 `a``b`
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch18-patterns-and-matching/listing-18-13/src/main.rs}}
@ -101,7 +101,7 @@ Rust 知道 `c` 位于第一个模式的范围内,并会打印出 `early ASCII
示例 18-14 展示了一个 `match` 语句将 `Point` 值分成了三种情况:直接位于 `x` 轴上(此时 `y = 0` 为真)、位于 `y` 轴上(`x = 0`)或不在任何轴上的点。
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch18-patterns-and-matching/listing-18-14/src/main.rs:here}}
@ -119,7 +119,7 @@ Rust 知道 `c` 位于第一个模式的范围内,并会打印出 `early ASCII
本书之前的部分曾经解构过枚举,比如第六章中示例 6-5 中解构了一个 `Option<i32>`。一个当时没有明确提到的细节是解构枚举的模式需要对应枚举所定义的储存数据的方式。让我们以示例 6-2 中的 `Message` 枚举为例,编写一个 `match` 使用模式解构每一个内部值,如示例 18-15 所示:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch18-patterns-and-matching/listing-18-15/src/main.rs}}
@ -147,7 +147,7 @@ Rust 知道 `c` 位于第一个模式的范围内,并会打印出 `early ASCII
<span class="caption">示例 18-16: 匹配嵌套的枚举</span>
`match` 表达式第一个分支的模式匹配一个包含 `Color::Rgb` 枚举成员的 `Message::ChangeColor` 枚举成员,然后模式绑定了 3 个内部的 `i32` 值。第二个分支的模式也匹配一个 `Message::ChangeColor` 枚举成员, 但是其内部的枚举会匹配 `Color::Hsv` 枚举成员。我们可以在一个 `match` 表达式中指定这些复杂条件,即使会涉及到两个枚举。
`match` 表达式第一个分支的模式匹配一个包含 `Color::Rgb` 枚举成员的 `Message::ChangeColor` 枚举成员,然后模式绑定了 3 个内部的 `i32` 值。第二个分支的模式也匹配一个 `Message::ChangeColor` 枚举成员,但是其内部的枚举会匹配 `Color::Hsv` 枚举成员。我们可以在一个 `match` 表达式中指定这些复杂条件,即使会涉及到两个枚举。
#### 解构结构体和元组
@ -169,7 +169,7 @@ Rust 知道 `c` 位于第一个模式的范围内,并会打印出 `early ASCII
我们已经使用过下划线(`_`)作为匹配但不绑定任何值的通配符模式了。虽然 `_` 模式作为 `match` 表达式最后的分支特别有用,也可以将其用于任意模式,包括函数参数中,如示例 18-17 所示:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch18-patterns-and-matching/listing-18-17/src/main.rs}}
@ -209,7 +209,7 @@ Rust 知道 `c` 位于第一个模式的范围内,并会打印出 `early ASCII
如果你创建了一个变量却不在任何地方使用它Rust 通常会给你一个警告,因为这可能会是个 bug。但是有时创建一个还未使用的变量是有用的比如你正在设计原型或刚刚开始一个项目。这时你希望告诉 Rust 不要警告未使用的变量,为此可以用下划线作为变量名的开头。示例 18-20 中创建了两个未使用变量,不过当编译代码时只会得到其中一个的警告:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch18-patterns-and-matching/listing-18-20/src/main.rs}}
@ -251,7 +251,7 @@ Rust 知道 `c` 位于第一个模式的范围内,并会打印出 `early ASCII
`..` 会扩展为所需要的值的数量。示例 18-24 展示了元组中 `..` 的应用:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch18-patterns-and-matching/listing-18-24/src/main.rs}}
@ -263,7 +263,7 @@ Rust 知道 `c` 位于第一个模式的范围内,并会打印出 `early ASCII
然而使用 `..` 必须是无歧义的。如果期望匹配和忽略的值是不明确的Rust 会报错。示例 18-25 展示了一个带有歧义的 `..` 例子,因此其不能编译:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch18-patterns-and-matching/listing-18-25/src/main.rs}}
@ -299,7 +299,7 @@ Rust 不可能决定在元组中匹配 `second` 值之前应该忽略多少个
在示例 18-11 中,我们提到可以使用匹配守卫来解决模式中变量覆盖的问题,那里 `match` 表达式的模式中新建了一个变量而不是使用 `match` 之外的同名变量。新变量意味着不能够测试外部变量的值。示例 18-27 展示了如何使用匹配守卫修复这个问题。
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch18-patterns-and-matching/listing-18-27/src/main.rs}}

View File

@ -157,11 +157,11 @@ Rust 的借用检查器不能理解我们要借用这个 slice 的两个不同
#### 使用 `extern` 函数调用外部代码
有时你的 Rust 代码可能需要与其他语言编写的代码交互。为此 Rust 有一个关键字,`extern`,有助于创建和使用 **外部函数接口***Foreign Function Interface* FFI。外部函数接口是一个编程语言用以定义函数的方式其允许不同外部编程语言调用这些函数。
有时你的 Rust 代码可能需要与其他语言编写的代码交互。为此 Rust 有一个关键字,`extern`,有助于创建和使用 **外部函数接口***Foreign Function Interface*FFI。外部函数接口是一个编程语言用以定义函数的方式其允许不同外部编程语言调用这些函数。
示例 19-8 展示了如何集成 C 标准库中的 `abs` 函数。`extern` 块中声明的函数在 Rust 代码中总是不安全的。因为其他语言不会强制执行 Rust 的规则且 Rust 无法检查它们,所以确保其安全是程序员的责任:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-08/src/main.rs}}
@ -192,7 +192,7 @@ Rust 的借用检查器不能理解我们要借用这个 slice 的两个不同
全局变量在 Rust 中被称为 **静态***static*)变量。示例 19-9 展示了一个拥有字符串 slice 值的静态变量的声明和应用:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-09/src/main.rs}}
@ -206,7 +206,7 @@ Rust 的借用检查器不能理解我们要借用这个 slice 的两个不同
常量与静态变量的另一个区别在于静态变量可以是可变的。访问和修改可变静态变量都是 **不安全** 的。示例 19-10 展示了如何声明、访问和修改名为 `COUNTER` 的可变静态变量:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-10/src/main.rs}}
@ -241,7 +241,7 @@ Rust 的借用检查器不能理解我们要借用这个 slice 的两个不同
使用 `unsafe` 来进行这五个操作(超能力)之一是没有问题的,甚至是不需要深思熟虑的,不过使得 `unsafe` 代码正确也实属不易,因为编译器不能帮助保证内存安全。当有理由使用 `unsafe` 代码时,是可以这么做的,通过使用显式的 `unsafe` 标注可以更容易地在错误发生时追踪问题的源头。
[dangling-references]:
ch04-02-references-and-borrowing.html#悬垂引用dangling-references
ch04-02-references-and-borrowing.html#悬垂引用 dangling-references
[differences-between-variables-and-constants]:
ch03-01-variables-and-mutability.html#常量
[extensible-concurrency-with-the-sync-and-send-traits]:

View File

@ -20,13 +20,13 @@
<span class="caption">示例 19-12: `Iterator` trait 的定义中带有关联类型 `Item`</span>
`Item` 是一个占位类型,同时 `next` 方法定义表明它返回 `Option<Self::Item>` 类型的值。这个 trait 的实现者会指定 `Item` 的具体类型,然而不管实现者指定何种类型, `next` 方法都会返回一个包含了此具体类型值的 `Option`
`Item` 是一个占位类型,同时 `next` 方法定义表明它返回 `Option<Self::Item>` 类型的值。这个 trait 的实现者会指定 `Item` 的具体类型,然而不管实现者指定何种类型`next` 方法都会返回一个包含了此具体类型值的 `Option`
关联类型看起来像一个类似泛型的概念,因为它允许定义一个函数而不指定其可以处理的类型。那么为什么要使用关联类型呢?
让我们通过一个在第十三章中出现的 `Counter` 结构体上实现 `Iterator` trait 的例子来检视其中的区别。在示例 13-21 中,指定了 `Item` 的类型为 `u32`
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch19-advanced-features/listing-13-21-reproduced/src/lib.rs:ch19}}
@ -52,7 +52,7 @@
Rust 并不允许创建自定义运算符或重载任意运算符,不过 `std::ops` 中所列出的运算符和相应的 trait 可以通过实现运算符相关 trait 来重载。例如,示例 19-14 中展示了如何在 `Point` 结构体上实现 `Add` trait 来重载 `+` 运算符,这样就可以将两个 `Point` 实例相加了:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-14/src/main.rs}}
@ -78,7 +78,7 @@ trait Add<Rhs=Self> {
这里有两个存放不同单元值的结构体,`Millimeters` 和 `Meters`。(这种将现有类型简单封装进另一个结构体的方式被称为 **newtype 模式***newtype pattern*,之后的 [“为了类型安全和抽象而使用 newtype 模式”][newtype] 部分会详细介绍。)我们希望能够将毫米值与米值相加,并让 `Add` 的实现正确处理转换。可以为 `Millimeters` 实现 `Add` 并以 `Meters` 作为 `Rhs`,如示例 19-15 所示。
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-15/src/lib.rs}}
@ -103,7 +103,7 @@ Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法
不过,当调用这些同名方法时,需要告诉 Rust 我们希望使用哪一个。考虑一下示例 19-16 中的代码,这里定义了 trait `Pilot``Wizard` 都拥有方法 `fly`。接着在一个本身已经实现了名为 `fly` 方法的类型 `Human` 上实现这两个 trait。每一个 `fly` 方法都进行了不同的操作:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-16/src/main.rs:here}}
@ -113,7 +113,7 @@ Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法
当调用 `Human` 实例的 `fly` 时,编译器默认调用直接实现在类型上的方法,如示例 19-17 所示。
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-17/src/main.rs:here}}
@ -125,7 +125,7 @@ Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法
为了能够调用 `Pilot` trait 或 `Wizard` trait 的 `fly` 方法,我们需要使用更明显的语法以便能指定我们指的是哪个 `fly` 方法。这个语法展示在示例 19-18 中:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-18/src/main.rs:here}}
@ -145,7 +145,7 @@ Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法
然而,关联函数是 trait 的一部分,但没有 `self` 参数。当同一作用域的两个类型实现了同一 traitRust 就不能计算出我们期望的是哪一个类型,除非使用 **完全限定语法***fully qualified syntax*)。例如,拿示例 19-19 中的 `Animal` trait 来说,它有关联函数 `baby_name`,结构体 `Dog` 实现了 `Animal`,同时有关联函数 `baby_name` 直接定义于 `Dog` 之上:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-19/src/main.rs}}
@ -163,7 +163,7 @@ Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法
这并不是我们需要的。我们希望调用的是 `Dog``Animal` trait 实现那部分的 `baby_name` 函数,这样能够打印出 `A baby dog is called a puppy`。示例 19-18 中用到的技术在这并不管用;如果将 `main` 改为示例 19-20 中的代码,则会得到一个编译错误:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-20/src/main.rs:here}}
@ -179,7 +179,7 @@ Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法
为了消歧义并告诉 Rust 我们希望使用的是 `Dog``Animal` 实现,需要使用 **完全限定语法**,这是调用函数时最为明确的方式。示例 19-21 展示了如何使用完全限定语法:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-21/src/main.rs:here}}
@ -203,7 +203,7 @@ Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法
### 父 trait 用于在另一个 trait 中使用某 trait 的功能
有时我们可能会需要某个 trait 使用另一个 trait 的功能。在这种情况下,需要能够依赖相关的 trait 也被实现。这个所需的 trait 是我们实现的 trait 的 **父(超) trait***supertrait*)。
有时我们可能会需要某个 trait 使用另一个 trait 的功能。在这种情况下,需要能够依赖相关的 trait 也被实现。这个所需的 trait 是我们实现的 trait 的 **父(超)trait***supertrait*)。
例如我们希望创建一个带有 `outline_print` 方法的 trait `OutlinePrint`,它会打印出带有星号框的值。也就是说,如果 `Point` 实现了 `Display` 并返回 `(x, y)`,调用以 `1` 作为 `x``3` 作为 `y``Point` 实例的 `outline_print` 会显示如下:
@ -217,7 +217,7 @@ Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法
`outline_print` 的实现中,因为希望能够使用 `Display` trait 的功能,则需要说明 `OutlinePrint` 只能用于同时也实现了 `Display` 并提供了 `OutlinePrint` 需要的功能的类型。可以通过在 trait 定义中指定 `OutlinePrint: Display` 来做到这一点。这类似于为 trait 增加 trait bound。示例 19-22 展示了一个 `OutlinePrint` trait 的实现:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-22/src/main.rs:here}}
@ -225,11 +225,11 @@ Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法
<span class="caption">示例 19-22: 实现 `OutlinePrint` trait它要求来自 `Display` 的功能</span>
因为指定了 `OutlinePrint` 需要 `Display` trait则可以在 `outline_print` 中使用 `to_string` 其会为任何实现 `Display` 的类型自动实现。如果不在 trait 名后增加 `: Display` 并尝试在 `outline_print` 中使用 `to_string`,则会得到一个错误说在当前作用域中没有找到用于 `&Self` 类型的方法 `to_string`
因为指定了 `OutlinePrint` 需要 `Display` trait则可以在 `outline_print` 中使用 `to_string`,其会为任何实现 `Display` 的类型自动实现。如果不在 trait 名后增加 `: Display` 并尝试在 `outline_print` 中使用 `to_string`,则会得到一个错误说在当前作用域中没有找到用于 `&Self` 类型的方法 `to_string`
让我们看看如果尝试在一个没有实现 `Display` 的类型上实现 `OutlinePrint` 会发生什么,比如 `Point` 结构体:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch19-advanced-features/no-listing-02-impl-outlineprint-for-point/src/main.rs:here}}
@ -243,7 +243,7 @@ Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法
一旦在 `Point` 上实现 `Display` 并满足 `OutlinePrint` 要求的限制,比如这样:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch19-advanced-features/no-listing-03-impl-display-for-point/src/main.rs:here}}
@ -257,7 +257,7 @@ Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法
例如,如果想要在 `Vec<T>` 上实现 `Display`,而孤儿规则阻止我们直接这么做,因为 `Display` trait 和 `Vec<T>` 都定义于我们的 crate 之外。可以创建一个包含 `Vec<T>` 实例的 `Wrapper` 结构体,接着可以如列表 19-23 那样在 `Wrapper` 上实现 `Display` 并使用 `Vec<T>` 的值:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-23/src/main.rs}}

View File

@ -10,7 +10,7 @@
我们讨论过了如何向函数传递闭包;也可以向函数传递常规函数!这在我们希望传递已经定义的函数而不是重新定义闭包作为参数时很有用。通过函数指针允许我们使用函数作为另一个函数的参数。函数的类型是 `fn` (使用小写的 “f” )以免与 `Fn` 闭包 trait 相混淆。`fn` 被称为 **函数指针***function pointer*)。指定参数为函数指针的语法类似于闭包,如示例 19-27 所示:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-27/src/main.rs}}

View File

@ -18,7 +18,7 @@
元编程对于减少大量编写和维护的代码是非常有用的,它也扮演了函数扮演的角色。但宏有一些函数所没有的附加能力。
一个函数签名必须声明函数参数个数和类型。相比之下,宏能够接收不同数量的参数:用一个参数调用 `println!("hello")` 或用两个参数调用 `println!("hello {}", name)` 。而且,宏可以在编译器翻译代码前展开,例如,宏可以在一个给定类型上实现 trait 。而函数则不行,因为函数是在运行时被调用,同时 trait 需要在编译时实现。
一个函数签名必须声明函数参数个数和类型。相比之下,宏能够接收不同数量的参数:用一个参数调用 `println!("hello")` 或用两个参数调用 `println!("hello {}", name)` 。而且,宏可以在编译器翻译代码前展开,例如,宏可以在一个给定类型上实现 trait。而函数则不行因为函数是在运行时被调用同时 trait 需要在编译时实现。
实现宏不如实现函数的一面是宏定义要比函数定义更复杂,因为你正在编写生成 Rust 代码的 Rust 代码。由于这样的间接性,宏定义通常要比函数定义更难阅读、理解以及维护。
@ -34,11 +34,11 @@ Rust 最常用的宏形式是 **声明宏***declarative macros*)。它们
let v: Vec<u32> = vec![1, 2, 3];
```
也可以使用 `vec!` 宏来构造两个整数的 vector 或五个字符串 slice 的 vector 。但却无法使用函数做相同的事情,因为我们无法预先知道参数值的数量和类型。
也可以使用 `vec!` 宏来构造两个整数的 vector 或五个字符串 slice 的 vector。但却无法使用函数做相同的事情因为我们无法预先知道参数值的数量和类型。
在示例 19-28 中展示了一个 `vec!` 稍微简化的定义。
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-28/src/lib.rs}}
@ -49,7 +49,7 @@ let v: Vec<u32> = vec![1, 2, 3];
> 注意:标准库中实际定义的 `vec!` 包括预分配适当量的内存的代码。这部分为代码优化,为了让示例简化,此处并没有包含在内。
`#[macro_export]` 注解表明只要导入了定义这个宏的crate该宏就应该是可用的。 如果没有该注解,这个宏不能被引入作用域。
`#[macro_export]` 注解表明只要导入了定义这个宏的 crate该宏就应该是可用的。如果没有该注解这个宏不能被引入作用域。
接着使用 `macro_rules!` 和宏名称开始宏定义,且所定义的宏并 **不带** 感叹号。名字后跟大括号表示宏定义体,在该例中宏名称是 `vec`
@ -91,7 +91,7 @@ let v: Vec<u32> = vec![1, 2, 3];
创建过程宏时,其定义必须驻留在它们自己的具有特殊 crate 类型的 crate 中。这么做出于复杂的技术原因,将来我们希望能够消除这些限制。使用这些宏需采用类似示例 19-29 所示的代码形式,其中 `some_attribute` 是一个使用特定宏的占位符。
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,ignore
use proc_macro;
@ -103,15 +103,15 @@ pub fn some_name(input: TokenStream) -> TokenStream {
<span class="caption">示例 19-29: 一个使用过程宏的例子</span>
定义过程宏的函数接收一个 TokenStream 作为输入并生成 TokenStream 作为输出。`TokenStream` 是定义于`proc_macro` crate里代表一系列token的类型Rust默认携带了`proc_macro` crate。 这就是宏的核心:宏所处理的源代码组成了输入 `TokenStream`,宏生成的代码是输出 `TokenStream`。函数上还有一个属性;这个属性指明了我们创建的过程宏的类型。在同一 crate 中可以有多种的过程宏。
定义过程宏的函数接收一个 TokenStream 作为输入并生成 TokenStream 作为输出。`TokenStream` 是定义于`proc_macro` crate 里代表一系列 token 的类型Rust 默认携带了`proc_macro` crate。这就是宏的核心宏所处理的源代码组成了输入 `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-30 中的代码。
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-30/src/main.rs}}
@ -127,13 +127,13 @@ $ cargo new hello_macro --lib
接下来,会定义 `HelloMacro` trait 以及其关联函数:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch19-advanced-features/no-listing-20-impl-hellomacro-for-pancakes/hello_macro/src/lib.rs}}
```
现在有了一个包含函数的 trait 。此时crate 用户可以实现该 trait 以达到其期望的功能,像这样:
现在有了一个包含函数的 trait。此时crate 用户可以实现该 trait 以达到其期望的功能,像这样:
```rust,ignore
{{#rustdoc_include ../listings/ch19-advanced-features/no-listing-20-impl-hellomacro-for-pancakes/pancakes/src/main.rs}}
@ -149,11 +149,11 @@ $ cargo new hello_macro --lib
$ cargo new hello_macro_derive --lib
```
由于两个 crate 紧密相关,因此在 `hello_macro` 包的目录下创建过程式宏的 crate。如果改变在 `hello_macro` 中定义的 trait ,同时也必须改变在 `hello_macro_derive` 中实现的过程式宏。这两个包需要分别发布,编程人员如果使用这些包,则需要同时添加这两个依赖并将其引入作用域。我们也可以只用 `hello_macro` 包而将 `hello_macro_derive` 作为一个依赖,并重新导出过程式宏的代码。但现在我们组织项目的方式使编程人员在无需 `derive` 功能时也能够单独使用 `hello_macro`
由于两个 crate 紧密相关,因此在 `hello_macro` 包的目录下创建过程式宏的 crate。如果改变在 `hello_macro` 中定义的 trait同时也必须改变在 `hello_macro_derive` 中实现的过程式宏。这两个包需要分别发布,编程人员如果使用这些包,则需要同时添加这两个依赖并将其引入作用域。我们也可以只用 `hello_macro` 包而将 `hello_macro_derive` 作为一个依赖,并重新导出过程式宏的代码。但现在我们组织项目的方式使编程人员在无需 `derive` 功能时也能够单独使用 `hello_macro`
我们需要声明 `hello_macro_derive` crate 是过程宏(proc-macro) crate。我们还需要 `syn``quote` crate 中的功能,正如你即将看到的,需要将他们加到依赖中。将下面的代码加入到 `hello_macro_derive`*Cargo.toml* 文件中。
我们需要声明 `hello_macro_derive` crate 是过程宏 (proc-macro) crate。我们还需要 `syn``quote` crate 中的功能,正如你即将看到的,需要将他们加到依赖中。将下面的代码加入到 `hello_macro_derive`*Cargo.toml* 文件中。
<span class="filename">文件名: hello_macro_derive/Cargo.toml</span>
<span class="filename">文件名hello_macro_derive/Cargo.toml</span>
```toml
{{#include ../listings/ch19-advanced-features/listing-19-31/hello_macro/hello_macro_derive/Cargo.toml:6:12}}
@ -161,7 +161,7 @@ $ cargo new hello_macro_derive --lib
为定义一个过程式宏,请将示例 19-31 中的代码放在 `hello_macro_derive` crate 的 *src/lib.rs* 文件里面。注意这段代码在我们添加 `impl_hello_macro` 函数的定义之前是无法编译的。
<span class="filename">文件名: hello_macro_derive/src/lib.rs</span>
<span class="filename">文件名hello_macro_derive/src/lib.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-31/hello_macro/hello_macro_derive/src/lib.rs}}
@ -204,7 +204,7 @@ DeriveInput {
<span class="caption">示例 19-32: 解析示例 19-30 中带有宏属性的代码时得到的 `DeriveInput` 实例</span>
该结构体的字段展示了我们解析的 Rust 代码是一个类单元结构体,其 `ident` identifier表示名字`Pancakes`。该结构体里面有更多字段描述了所有类型的 Rust 代码,查阅 [`syn` 中 `DeriveInput` 的文档][syn-docs] 以获取更多信息。
该结构体的字段展示了我们解析的 Rust 代码是一个类单元结构体,其 `ident`identifier表示名字`Pancakes`。该结构体里面有更多字段描述了所有类型的 Rust 代码,查阅 [`syn` 中 `DeriveInput` 的文档][syn-docs] 以获取更多信息。
[syn-docs]: https://docs.rs/syn/0.14.4/syn/struct.DeriveInput.html
@ -214,7 +214,7 @@ DeriveInput {
现在我们有了将注解的 Rust 代码从 `TokenStream` 转换为 `DeriveInput` 实例的代码,让我们来创建在注解类型上实现 `HelloMacro` trait 的代码,如示例 19-33 所示。
<span class="filename">文件名: hello_macro_derive/src/lib.rs</span>
<span class="filename">文件名hello_macro_derive/src/lib.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-33/hello_macro/hello_macro_derive/src/lib.rs:here}}
@ -232,7 +232,7 @@ DeriveInput {
我们期望我们的过程式宏能够为通过 `#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` 转换为字符串来节省内存分配。
此处所使用的 `stringify!` 为 Rust 内置宏。其接收一个 Rust 表达式,如 `1 + 2` ,然后在编译时将表达式转换为一个字符串常量,如 `"1 + 2"` 。这与 `format!``println!` 是不同的,它计算表达式并将结果转换为 `String` 。有一种可能的情况是,所输入的 `#name` 可能是一个需要打印的表达式,因此我们用 `stringify!` 。`stringify!` 也能通过在编译时将 `#name` 转换为字符串来节省内存分配。
此时,`cargo build` 应该都能成功编译 `hello_macro``hello_macro_derive` 。我们将这些 crate 连接到示例 19-30 的代码中来看看过程宏的行为!在 *projects* 目录下用 `cargo new pancakes` 命令新建一个二进制项目。需要将 `hello_macro``hello_macro_derive` 作为依赖加到 `pancakes` 包的 *Cargo.toml* 文件中去。如果你正将 `hello_macro``hello_macro_derive` 的版本发布到 [crates.io](https://crates.io/) 上,其应为常规依赖;如果不是,则可以像下面这样将其指定为 `path` 依赖:

View File

@ -1,4 +1,4 @@
# 最后的项目: 构建多线程 web server
# 最后的项目构建多线程 web server
> [ch20-00-final-project-a-web-server.md](https://github.com/rust-lang/book/blob/main/src/ch20-00-final-project-a-web-server.md)
> <br>

View File

@ -6,7 +6,7 @@
首先让我们创建一个可运行的单线程 web server不过在开始之前我们将快速了解一下构建 web server 所涉及到的协议。这些协议的细节超出了本书的范畴,不过一个简单的概括会提供我们所需的信息。
web server 中涉及到的两个主要协议是 **超文本传输协议***Hypertext Transfer Protocol**HTTP*)和 **传输控制协议***Transmission Control Protocol**TCP*)。这两者都是 **请求-响应***request-response*)协议,也就是说,有 **客户端***client*)来初始化请求,并有 **服务端***server*)监听请求并向客户端提供响应。请求与响应的内容由协议本身定义。
web server 中涉及到的两个主要协议是 **超文本传输协议***Hypertext Transfer Protocol**HTTP*)和 **传输控制协议***Transmission Control Protocol**TCP*)。这两者都是 **请求 - 响应***request-response*)协议,也就是说,有 **客户端***client*)来初始化请求,并有 **服务端***server*)监听请求并向客户端提供响应。请求与响应的内容由协议本身定义。
TCP 是一个底层协议,它描述了信息如何从一个 server 到另一个的细节不过其并不指定信息是什么。HTTP 构建于 TCP 之上,它定义了请求和响应的内容。为此,技术上讲可将 HTTP 用于其他协议之上不过对于绝大部分情况HTTP 通过 TCP 传输。我们将要做的就是处理 TCP 和 HTTP 请求与响应的原始字节数据。
@ -22,7 +22,7 @@ $ cd hello
并在 `src/main.rs` 输入示例 20-1 中的代码作为开始。这段代码会在地址 `127.0.0.1:7878` 上监听传入的 TCP 流。当获取到传入的流,它会打印出 `Connection established!`
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,no_run
{{#rustdoc_include ../listings/ch20-web-server/listing-20-01/src/main.rs}}
@ -59,7 +59,7 @@ Connection established!
让我们实现读取来自浏览器请求的功能!为了分离获取连接和接下来对连接的操作的相关内容,我们将开始一个新函数来处理连接。在这个新的 `handle_connection` 函数中,我们从 TCP 流中读取数据并打印出来以便观察浏览器发送过来的数据。将代码修改为如示例 20-2 所示:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,no_run
{{#rustdoc_include ../listings/ch20-web-server/listing-20-02/src/main.rs}}
@ -112,7 +112,7 @@ message-body
请求行接下来的部分是 */*,它代表客户端请求的 **统一资源标识符***Uniform Resource Identifier**URI* —— URI 大体上类似,但也不完全类似于 URL**统一资源定位符***Uniform Resource Locators*。URI 和 URL 之间的区别对于本章的目的来说并不重要,不过 HTTP 规范使用术语 URI所以这里可以简单的将 URL 理解为 URI。
最后一部分是客户端使用的HTTP版本然后请求行以 **CRLF序列** CRLF代表回车和换行*carriage return line feed*这是打字机时代的术语结束。CRLF序列也可以写成`\r\n`,其中`\r`是回车符,`\n`是换行符。 CRLF序列将请求行与其余请求数据分开。 请注意打印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` 是版本。
@ -142,7 +142,7 @@ HTTP/1.1 200 OK\r\n\r\n
状态码 200 是一个标准的成功响应。这些文本是一个微型的成功 HTTP 响应。让我们将这些文本写入流作为成功请求的响应!在 `handle_connection` 函数中,我们需要去掉打印请求数据的 `println!`,并替换为示例 20-3 中的代码:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,no_run
{{#rustdoc_include ../listings/ch20-web-server/listing-20-03/src/main.rs:here}}
@ -160,7 +160,7 @@ HTTP/1.1 200 OK\r\n\r\n
让我们实现不只是返回空页面的功能。在项目根目录创建一个新文件,*hello.html* —— 也就是说,不是在 `src` 目录。在此可以放入任何你期望的 HTML列表 20-4 展示了一个可能的文本:
<span class="filename">文件名: hello.html</span>
<span class="filename">文件名hello.html</span>
```html
{{#include ../listings/ch20-web-server/listing-20-04/hello.html}}
@ -170,7 +170,7 @@ HTTP/1.1 200 OK\r\n\r\n
这是一个极小化的 HTML5 文档,它有一个标题和一小段文本。为了在 server 接受请求时返回它,需要如示例 20-5 所示修改 `handle_connection` 来读取 HTML 文件,将其加入到响应的 body 中,并发送:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,no_run
{{#rustdoc_include ../listings/ch20-web-server/listing-20-05/src/main.rs:here}}
@ -190,7 +190,7 @@ HTTP/1.1 200 OK\r\n\r\n
目前我们的 web server 不管客户端请求什么都会返回相同的 HTML 文件。让我们增加在返回 HTML 文件前检查浏览器是否请求 */*,并在其请求任何其他内容时返回错误的功能。为此需要如示例 20-6 那样修改 `handle_connection`。新代码接收到的请求的内容与已知的 */* 请求的一部分做比较,并增加了 `if``else` 块来区别处理请求:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,no_run
{{#rustdoc_include ../listings/ch20-web-server/listing-20-06/src/main.rs:here}}
@ -206,7 +206,7 @@ HTTP/1.1 200 OK\r\n\r\n
现在向示例 20-7 的 `else` 块增加代码来返回一个带有 404 状态码的响应,这代表了所请求的内容没有找到。接着也会返回一个 HTML 向浏览器终端用户表明此意:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,no_run
{{#rustdoc_include ../listings/ch20-web-server/listing-20-07/src/main.rs:here}}
@ -216,7 +216,7 @@ HTTP/1.1 200 OK\r\n\r\n
这里,响应的状态行有状态码 404 和原因短语 `NOT FOUND`。仍然没有返回任何 header而其 body 将是 *404.html* 文件中的 HTML。需要在 *hello.html* 同级目录创建 *404.html* 文件作为错误页面;这一次也可以随意使用任何 HTML 或使用示例 20-8 中的示例 HTML
<span class="filename">文件名: 404.html</span>
<span class="filename">文件名404.html</span>
```html
{{#include ../listings/ch20-web-server/listing-20-08/404.html}}
@ -230,7 +230,7 @@ HTTP/1.1 200 OK\r\n\r\n
目前 `if``else` 块中的代码有很多的重复:他们都读取文件并将其内容写入流。唯一的区别是状态行和文件名。为了使代码更为简明,将这些区别分别提取到一行 `if``else` 中,对状态行和文件名变量赋值;然后在读取文件和写入响应的代码中无条件的使用这些变量。重构后取代了大段 `if``else` 块代码后的结果如示例 20-9 所示:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,no_run
{{#rustdoc_include ../listings/ch20-web-server/listing-20-09/src/main.rs:here}}

View File

@ -10,7 +10,7 @@
让我们看看一个慢请求如何影响当前 server 实现中的其他请求。示例 20-10 通过模拟慢响应实现了 */sleep* 请求处理,它会使 server 在响应之前休眠五秒。
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,no_run
{{#rustdoc_include ../listings/ch20-web-server/listing-20-10/src/main.rs:here}}
@ -30,7 +30,7 @@
**线程池***thread pool*)是一组预先分配的等待或准备处理任务的线程。当程序收到一个新任务,线程池中的一个线程会被分配任务,这个线程会离开并处理任务。其余的线程则可用于处理在第一个线程处理任务的同时处理其他接收到的任务。当第一个线程处理完任务时,它会返回空闲线程池中等待处理新任务。线程池允许我们并发处理连接,增加 server 的吞吐量。
我们会将池中线程限制为较少的数量以防拒绝服务Denial of Service DoS攻击如果程序为每一个接收的请求都新建一个线程某人向 server 发起千万级的请求时会耗尽服务器的资源并导致所有请求的处理都被终止。
我们会将池中线程限制为较少的数量以防拒绝服务Denial of ServiceDoS攻击如果程序为每一个接收的请求都新建一个线程某人向 server 发起千万级的请求时会耗尽服务器的资源并导致所有请求的处理都被终止。
不同于分配无限的线程,线程池中将有固定数量的等待线程。当新进请求时,将请求发送到线程池中做处理。线程池会维护一个接收请求的队列。每一个线程会从队列中取出一个请求,处理请求,接着向队列索取另一个请求。通过这种设计,则可以并发处理 `N` 个请求,其中 `N` 为线程数。如果每一个线程都在响应慢请求,之后的请求仍然会阻塞队列,不过相比之前增加了能处理的慢请求的数量。
@ -44,7 +44,7 @@
首先,让我们探索一下为每一个连接都创建一个线程的代码看起来如何。这并不是最终方案,因为正如之前讲到的它会潜在的分配无限的线程,不过这是一个开始。示例 20-11 展示了 `main` 的改变,它在 `for` 循环中为每一个流分配了一个新线程进行处理:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,no_run
{{#rustdoc_include ../listings/ch20-web-server/listing-20-11/src/main.rs:here}}
@ -58,7 +58,7 @@
我们期望线程池以类似且熟悉的方式工作,以便从线程切换到线程池并不会对使用该 API 的代码做出较大的修改。示例 20-12 展示我们希望用来替换 `thread::spawn``ThreadPool` 结构体的假想接口:
<span class="filename">文件名: src/main.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch20-web-server/listing-20-12/src/main.rs:here}}
@ -80,7 +80,7 @@
创建 *src/lib.rs* 文件,它包含了目前可用的最简单的 `ThreadPool` 定义:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch20-web-server/no-listing-01-define-threadpool-struct/src/lib.rs}}
@ -88,7 +88,7 @@
接着创建一个新目录,*src/bin*,并将二进制 crate 根文件从 *src/main.rs* 移动到 *src/bin/main.rs*。这使得库 crate 成为 *hello* 目录的主要 crate不过仍然可以使用 `cargo run` 运行 *src/bin/main.rs* 二进制文件。移动了 *main.rs* 文件之后,修改 *src/bin/main.rs* 文件开头加入如下代码来引入库 crate 并将 `ThreadPool` 引入作用域:
<span class="filename">文件名: src/bin/main.rs</span>
<span class="filename">文件名src/bin/main.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch20-web-server/no-listing-01-define-threadpool-struct/src/bin/main.rs:here}}
@ -102,7 +102,7 @@
这告诉我们下一步是为 `ThreadPool` 创建一个叫做 `new` 的关联函数。我们还知道 `new` 需要有一个参数可以接受 `4`,而且 `new` 应该返回 `ThreadPool` 实例。让我们实现拥有此特征的最小化 `new` 函数:
<span class="filename">文件夹: src/lib.rs</span>
<span class="filename">文件夹src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch20-web-server/no-listing-02-impl-threadpool-new/src/lib.rs}}
@ -132,7 +132,7 @@ pub fn spawn<F, T>(f: F) -> JoinHandle<T>
`F` 还有 trait bound `Send` 和生命周期绑定 `'static`,这对我们的情况也是有意义的:需要 `Send` 来将闭包从一个线程转移到另一个线程,而 `'static` 是因为并不知道线程会执行多久。让我们编写一个使用带有这些 bound 的泛型参数 `F``ThreadPool``execute` 方法:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch20-web-server/no-listing-03-define-execute/src/lib.rs:here}}
@ -154,7 +154,7 @@ pub fn spawn<F, T>(f: F) -> JoinHandle<T>
这里仍然存在警告是因为其并没有对 `new``execute` 的参数做任何操作。让我们用期望的行为来实现这些函数。以考虑 `new` 作为开始。之前选择使用无符号类型作为 `size` 参数的类型,因为线程数为负的线程池没有意义。然而,线程数为零的线程池同样没有意义,不过零是一个完全有效的 `u32` 值。让我们增加在返回 `ThreadPool` 实例之前检查 `size` 是否大于零的代码,并使用 `assert!` 宏在得到零时 panic如示例 20-13 所示:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch20-web-server/listing-20-13/src/lib.rs:here}}
@ -186,7 +186,7 @@ pub fn spawn<F, T>(f: F) -> JoinHandle<T>
示例 20-14 中的代码可以编译,不过实际上还并没有创建任何线程。我们改变了 `ThreadPool` 的定义来存放一个 `thread::JoinHandle<()>` 的 vector 实例,使用 `size` 容量来初始化,并设置一个 `for` 循环了来运行创建线程的代码,并返回包含这些线程的 `ThreadPool` 实例:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,ignore,not_desired_behavior
{{#rustdoc_include ../listings/ch20-web-server/listing-20-14/src/lib.rs:here}}
@ -219,7 +219,7 @@ pub fn spawn<F, T>(f: F) -> JoinHandle<T>
准备好了吗?示例 20-15 就是一个做出了这些修改的例子:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch20-web-server/listing-20-15/src/lib.rs:here}}
@ -249,7 +249,7 @@ pub fn spawn<F, T>(f: F) -> JoinHandle<T>
让我们以在 `ThreadPool::new` 中创建信道并让 `ThreadPool` 实例充当发送端开始,如示例 20-16 所示。`Job` 是将在信道中发出的类型,目前它是一个没有任何内容的结构体:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch20-web-server/listing-20-16/src/lib.rs:here}}
@ -261,7 +261,7 @@ pub fn spawn<F, T>(f: F) -> JoinHandle<T>
让我们尝试在线程池创建每个 worker 时将信道的接收端传递给他们。须知我们希望在 worker 所分配的线程中使用信道的接收端,所以将在闭包中引用 `receiver` 参数。示例 20-17 中展示的代码还不能编译:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch20-web-server/listing-20-17/src/lib.rs:here}}
@ -283,7 +283,7 @@ pub fn spawn<F, T>(f: F) -> JoinHandle<T>
回忆一下第十六章讨论的线程安全智能指针,为了在多个线程间共享所有权并允许线程修改其值,需要使用 `Arc<Mutex<T>>`。`Arc` 使得多个 worker 拥有接收端,而 `Mutex` 则确保一次只有一个 worker 能从接收端得到任务。示例 20-18 展示了所需的修改:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch20-web-server/listing-20-18/src/lib.rs:here}}
@ -299,7 +299,7 @@ pub fn spawn<F, T>(f: F) -> JoinHandle<T>
最后让我们实现 `ThreadPool` 上的 `execute` 方法。同时也要修改 `Job` 结构体:它将不再是结构体,`Job` 将是一个有着 `execute` 接收到的闭包类型的 trait 对象的类型别名。第十九章 [“类型别名用来创建类型同义词”][creating-type-synonyms-with-type-aliases] 部分提到过,类型别名允许将长的类型变短。观察示例 20-19
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch20-web-server/listing-20-19/src/lib.rs:here}}
@ -311,7 +311,7 @@ pub fn spawn<F, T>(f: F) -> JoinHandle<T>
不过到此事情还没有结束!在 worker 中,传递给 `thread::spawn` 的闭包仍然还只是 **引用** 了信道的接收端。相反我们需要闭包一直循环,向信道的接收端请求任务,并在得到任务时执行他们。如示例 20-20 对 `Worker::new` 做出修改:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch20-web-server/listing-20-20/src/lib.rs:here}}
@ -372,7 +372,7 @@ Worker 2 got a job; executing.
在学习了第十八章的 `while let` 循环之后,你可能会好奇为何不能如此编写 worker 线程,如示例 20-21 所示:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,ignore,not_desired_behavior
{{#rustdoc_include ../listings/ch20-web-server/listing-20-21/src/lib.rs:here}}

View File

@ -12,7 +12,7 @@
现在开始为线程池实现 `Drop`。当线程池被丢弃时,应该 join 所有线程以确保他们完成其操作。示例 20-22 展示了 `Drop` 实现的第一次尝试;这些代码还不能够编译:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch20-web-server/listing-20-22/src/lib.rs:here}}
@ -32,7 +32,7 @@
为此需要更新 `Worker` 的定义为如下:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch20-web-server/no-listing-04-update-worker-definition/src/lib.rs:here}}
@ -46,7 +46,7 @@
让我们修复第二个错误,它指向 `Worker::new` 结尾的代码;当新建 `Worker` 时需要将 `thread` 值封装进 `Some`。做出如下改变以修复问题:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch20-web-server/no-listing-05-fix-worker-new/src/lib.rs:here}}
@ -54,7 +54,7 @@
第一个错误位于 `Drop` 实现中。之前提到过要调用 `Option` 上的 `take``thread` 移动出 `worker`。如下改变会修复问题:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch20-web-server/no-listing-06-fix-threadpool-drop/src/lib.rs:here}}
@ -68,7 +68,7 @@
为了修复这个问题,修改线程既监听是否有 `Job` 运行也要监听一个应该停止监听并退出无限循环的信号。所以信道将发送这个枚举的两个成员之一而不是 `Job` 实例:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch20-web-server/no-listing-07-define-message-enum/src/lib.rs:here}}
@ -78,7 +78,7 @@
同时需要修改信道来使用 `Message` 类型值而不是 `Job`,如示例 20-23 所示:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch20-web-server/listing-20-23/src/lib.rs:here}}
@ -90,7 +90,7 @@
通过这些修改,代码再次能够编译并继续按照示例 20-20 之后相同的行为运行。不过还是会得到一个警告,因为并没有创建任何 `Terminate` 成员的消息。如示例 20-24 所示修改 `Drop` 实现来修复此问题:
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch20-web-server/listing-20-24/src/lib.rs:here}}
@ -106,7 +106,7 @@
为了实践这些代码,如示例 20-25 所示修改 `main` 在优雅停机 server 之前只接受两个请求:
<span class="filename">文件名: src/bin/main.rs</span>
<span class="filename">文件名src/bin/main.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch20-web-server/listing-20-25/src/bin/main.rs:here}}
@ -140,7 +140,7 @@ Shutting down worker 2
Shutting down worker 3
```
可能会出现不同顺序的 worker 和信息输出。可以从信息中看到服务是如何运行的: worker 0 和 worker 3 获取了头两个请求,接着在第三个请求时,我们停止接收连接。当 `ThreadPool``main` 的结尾离开作用域时,其 `Drop` 实现开始工作,线程池通知所有线程终止。每个 worker 在收到终止消息时会打印出一个信息,接着线程池调用 `join` 来终止每一个 worker 线程。
可能会出现不同顺序的 worker 和信息输出。可以从信息中看到服务是如何运行的worker 0 和 worker 3 获取了头两个请求,接着在第三个请求时,我们停止接收连接。当 `ThreadPool``main` 的结尾离开作用域时,其 `Drop` 实现开始工作,线程池通知所有线程终止。每个 worker 在收到终止消息时会打印出一个信息,接着线程池调用 `join` 来终止每一个 worker 线程。
这个特定的运行过程中一个有趣的地方在于:注意我们向信道中发出终止消息,而在任何线程收到消息之前,就尝试 join worker 0 了。worker 0 还没有收到终止消息,所以主线程阻塞直到 worker 0 结束。与此同时,每一个线程都收到了终止消息。一旦 worker 0 结束,主线程就等待其他 worker 结束,此时他们都已经收到终止消息并能够停止了。
@ -148,13 +148,13 @@ Shutting down worker 3
如下是完整的代码参考:
<span class="filename">文件名: src/bin/main.rs</span>
<span class="filename">文件名src/bin/main.rs</span>
```rust,ignore
{{#rustdoc_include ../listings/ch20-web-server/no-listing-08-final-code/src/bin/main.rs}}
```
<span class="filename">文件名: src/lib.rs</span>
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch20-web-server/no-listing-08-final-code/src/lib.rs}}