Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
15 changed files
with
2,627 additions
and
2,556 deletions.
There are no files selected for viewing
1,613 changes: 5 additions & 1,608 deletions
1,613
src/Ch20_Final_Project_Building_a_Multithreaded_Web_Server.md
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
# 附录 C:派生特质 | ||
|
||
**Appendix C: Derivable Traits** | ||
|
||
|
||
本书的多个不同地方,咱们都曾讨论过 `derive` 属性,咱们可将其应用到结构体或枚举定义。`derive` 属性会在咱们以 `derive` 语法注解的类型上,生成将以某个特质自身默认实现,而实现该特质的代码。 | ||
|
||
在这个附录中,咱们会提供到标准库中,咱们可以与 `derive` 一起使用的全部特质的参考。以下各个小节均会讲到: | ||
|
||
- 此特质将启用那些操作符与方法; | ||
|
||
- 由 `derive` 所提供到的该特质实现会做些什么; | ||
|
||
- 实现该特质对那个类型意味着什么; | ||
|
||
- 允许及不允许实现该特质的情况; | ||
|
||
- 需要该特质操作的示例。 | ||
|
||
|
||
|
||
若咱们想要不同于由 `derive` 属性所提供的行为,请参考 [标准库文档](https://doc.rust-lang.org/std/index.html),了解如何亲自实现各个特质的详细信息。 | ||
|
||
这里列出的这些特质,只是一些由标准库所提供的,可使用 `derive` 实现于咱们类型上的那些。定义在标准库中别的一些特质,则没有什么合理的默认行为,因此是否要以对于咱们正尝试完成的东西有意义的方式,实现他们就取决于咱们自己了。 | ||
|
||
不能派生的一个特质示例便是 `Display`,其为终端用户处理格式化。咱们应始终要考虑将某个类型显示给用户的恰当方式。终端用户应被允许看到该类型的哪些部分?他们会发现哪些部分是相关的?数据的何种形式才是与他们最为密切相关的?Rust 编译器并无这种见解,因此他就无法为咱们提供到恰当的默认行为。 | ||
|
||
这个附录中所提供到的派生特质清单并不详尽:库可以为他们自己的特质实现 `derive`,从而领导咱们可使用 `derive` 的特质清单为真正开放的。实现 `derive` 设计到使用程序性宏,这在第 19 章的 [“关于宏”](Ch19_Advanced_Features.md#关于宏) 小节讲到过。 | ||
|
||
|
||
## 输出给编程者的 `Debug` | ||
|
||
**`Debug` for Programmer Output** | ||
|
||
|
||
`Debug` 特质实现了格式字符串中的格式化,所谓格式字符串,即咱们通过在 `{}` 里添加 `:?` 所表示的。 | ||
|
||
`Debug` 特质允许咱们为调试目的打印某种类型的实例,如此咱们以及用到咱们类型的其他编程者,就可以在程序执行的某个特定时刻,就其某个实例加以探查。 | ||
|
||
在比如用到 `assert_eq!` 宏中等情况下,`Debug` 特质便是要求使用的。`assert_eq!` 这个宏在相等断言失败时,就会打印出作为参数所给到的两个实例值,如此编程者就可以看到为何这两个实例不相等。 | ||
|
||
|
||
## 用于相等比较的 `PartialEq` 与 `Eq` | ||
|
||
|
||
`PartialEq` 特质允许咱们比较某种类型的两个实例,来检查他们是否相等,并实现 `==` 与 `!=` 运算符的应用。 | ||
|
||
对 `PartialEq` 进行派生,就会实现 `eq` 方法。当 `ParitalEq` 实在结构体上实现的时,只有在两个实例的 *全部* 字段都相等时,他们才是相等的,且在有任何字段不等时,两个实例便不相等。当在枚举上派生时,枚举的各个变种与自身相等,而不等于其他任何变种。 | ||
|
||
在使用需要能够比较某个类型的两个实例是否相等的 `assert_eq!` 宏时,就需要这个 `PartialEq` 特质。 | ||
|
||
而 `Eq` 特质则没有方法。他的目的是要表明,所注解的类型的每个值,其值都等于他自身。尽管并非所有实现 `PartialEq` 的类型都可以实现 `Eq`,但 `Eq` 特质却只可应用到那些同时实现了 `PartialEq` 的类型。这方面的一个示例,便是浮点数类型:浮点数的实现,就表明两个非数字(the not-a-number, `NaN`)的值,是各自不相等的。 | ||
|
||
要求 `Eq` 的一个示例,就是 `HashMap<K, V>` 中的那些键,如此 `HashMap<K, V>` 就可以区分出两个键是否一致。 | ||
|
||
|
||
## 用于排序比较的 `PartialOrd` 与 `Ord` | ||
|
||
|
||
**`PartialOrd` and `Ord` for Ordering Comparisons** | ||
|
||
`PartialOrd` 特质实现为排序目的,而比较某种类型的那些实例。实现了 `PartialOrd` 的类型,便可与 `<`、`>`、`<=` 及 `>=` 符号一起使用了。咱们只能对那些同时实现了 `PartialEq` 的类型,应用这个 `PartialOrd` 特质。 | ||
|
||
派生 `PartialOrd`,会实现 `partial_cmp` 方法,该方法会返回一个在所给的那些值不会产生出顺序时,将为 `None` 的一个 `Option<Ordering>`。至于即使那种类型的大多数值都可被比较,但仍不会产生出顺序的值的一个示例,便是非数字(`NaN`)浮点值。在任何浮点数和非数字浮点值下调用 `partial_cmp`,都会返回 `None`。 | ||
|
||
在于结构体上派生时,`PartialOrd` 会通过字段出现在结构体定义中的顺序,比较每个字段中的值,比较两个实例。而当于枚举上派生时,枚举定义中较早声明的枚举变种,被当作是小于后面所列出的那些变种的。 | ||
|
||
在比如会产生出由范围表达式所指定范围中一个随机数的, `rand` 代码箱的 `gen_range` 方法来说,`PartialOrd` 特质便是需要的。 | ||
|
||
`Ord` 特质实现对所注解类型的任何两个值,将存在有效顺序的掌握。`Ord` 特质会实现 `cmp` 方法,由于有效排序将始终可行,因此该方法返回的是 `Ordering` 而非 `Option<Ordering>`。咱们只可对那些同时实现了 `PartialOrd` 及 `Eq` (而 `Eq` 要求 `PartialEq`) 的类型,实现这个 `Ord` 特质。当于结构体及枚举上派生 `Ord` 时,`cmp` 就会以与 `PartialOrd` 下 `partial_cmp` 的派生实现同样方式行事。 | ||
|
||
要求 `Ord` 的一个示例,即为将一些值存储在 `BTreeSet<T>` 这种根据值的排序,而存储数据的数据结构中时。 | ||
|
||
|
||
## 用于复制值的 `Clone` 与 `Copy` | ||
|
||
**`Clone` and `Copy` for Duplicating Values** | ||
|
||
|
||
`Clone` 特质实现了显式创建值的深拷贝,而该复制过程则可能涉及运行一些任意代码,arbitary code,与拷贝内存堆数据。请参阅第 4 章中 [“变量与数据交互方式:克隆”](Ch04_Understanding_Ownership.md#变量与数据交互方式之二克隆) 小节,了解更多有关 `Clone` 的信息。 | ||
|
||
派生 `Clone` 会实现 `clone` 方法,当对整个类型实现了这个方法时,其就会在该类型的各个部分上调用 `clone`。这意味着类型要派生 `Clone` 其中的全部字段或值,都必须同时实现 `Clone`。 | ||
|
||
需要 `Clone` 特质的一个示例,便是在切片上调用 `to_vec` 方法时。切片不持有其包含的那些类型实例,但自 `to_vec` 所返回的那个矢量值,却将需要持有他的那些实例,从而 `to_vec` 会调用各个条目上的 `clone`。因此,存储在切片中的类型,就必须实现 `Clone`。 | ||
|
||
`Copy` 特质实现了只通过拷贝存储在栈上的二进制位,而复制某个值;任意代码并无必要。请参阅第 4 章中 [“唯栈数据:拷贝”](Ch04_Understanding_Ownership.md#唯栈数据拷贝stack-only-data-copy),了解更多有关 `Copy` 的信息。 | ||
|
||
`Copy` 特质没有定义阻止编程者过载那些方法,及破坏不会有任意代码运行这个假设的任何方法。那样的话,所有编程者就都可以假定,拷贝值将会非常快。 | ||
|
||
咱们可在其组成部分都实现了 `Copy` 的任何类型上派生 `Copy` 特质。由于实现 `Copy` 的类型,都有着执行与 `Copy` 同样任务的一个 `Clone` 的简单实现,因此实现 `Copy` 的类型必须同时实现 `Clone`。 | ||
|
||
很少需要 `Copy` 特质;实现了 `Copy` 的类型,有着可供选择的优化方案,意味着咱们不必调用 `clone`,而调用 `clone` 会令到代码更简洁。 | ||
|
||
对于 `Copy` 下每种可能情况,咱们都可同时以 `Clone` 完成,除了代码可能更慢,或在一些地方不得不使用 `clone`。 | ||
|
||
|
||
## 用于将值映射到固定大小值的 `Hash` | ||
|
||
**`Hash` for Mapping a Value to a Value of Fixed Size** | ||
|
||
|
||
`Hash` 特质实现了取某种任意大小类型的实例,并通过使用散列函数,将那个实例映射到固定大小的值。派生 `Hash` 会实现 `hash` 方法。`hash` 放的派生实现,会将在该类型各个组成部分上调用 `hash` 的结果结合起来,这就意味着类型要派生 `Hash`,那么其全部字段,都必须同时实现 `Hash`。 | ||
|
||
要求 `Hash` 的一个示例,便是为了高效地存储数据,而在 `Hash<K, V>` 中存储那些键时。 | ||
|
||
|
||
## 用于默认值的 `Default` | ||
|
||
**`Default` for Default Values** | ||
|
||
|
||
`Default` 特质实现了为类型创建出一个默认值。派生 `Default` 会实现 `default` 函数。`default` 函数的派生实现,会在类型的各个部分上调用 `default` 函数,意味类型要派生 `Defualt`,其中的全部字段或值,都必须同时实现 `Default`。 | ||
|
||
`Default::default` 函数,通常是与第 5 章中 [“使用结构体更新语法从其他实例创建出实例”](Ch05_Using_Structs_to_Structure_Related_Data.md#使用结构体更新语法从其他实例创建出实例) 小节里曾讨论过的结构体更新语法结合使用的。咱们可以定制结构体的几个字段,并在随后通过使用 `..Default::default()`,为其余字段设置并使用默认值。 | ||
|
||
在 `Option<T>` 实例上使用 `unwrap_or_default` 方法时,便是需要 `Default` 特质的一个示例。当那个 `Option<T>` 为 `None` 时,方法 `unwrap_or_default` 就将返回存储在 `Option<T>` 中,那个类型 `T` 的 `Default::default` 结果。 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
# 附录 D:一些有用开发工具 | ||
|
||
在此附录中,咱们会讲到 Rust 项目所提供的一些有用的开发工具。咱们将看看自动格式化、应用警告修复的一些快速方法、一种代码静态分析工具,a linter,以及与多种 IDE 的集成。 | ||
|
||
|
||
## 使用 `rustfmt` 的自动格式化 | ||
|
||
**Automatic Formatting with `rustfmt`** | ||
|
||
|
||
`rustfmt` 工具会依据社区编码风格,重新格式化咱们的代码。许多协作项目,都使用了 `rustfmt` 来防止有关编写 Rust 时使用何种风格方面的争论:每个人都使用这个工具来格式化他们的代码。 | ||
|
||
要安装 `rustfmt`,请键入下面的命令: | ||
|
||
```console | ||
$ rustup component add rustfmt | ||
``` | ||
|
||
如同 Rust 会同时给到 `rustc` 与 `cargo` 一样,此命令会给到咱们 `rustfmt` 与 `cargo-fmt`。要格式化任何 Cargo 项目,请敲入下面的命令: | ||
|
||
```console | ||
$ cargo fmt | ||
``` | ||
|
||
运行此命令,会重新格式化当前代码箱中全部的 Rust 代码。这只会改变编码风格,而不会改变代码语义。关于 `rustfmt` 的更多信息,请参阅 [其文档](https://github.com/rust-lang/rustfmt). | ||
|
||
|
||
## 使用 `rustfix` 修复咱们的代码 | ||
|
||
**Fix Your Code with `rustfix`** | ||
|
||
|
||
`rustfix` 工具已被 Rust 安装所包含,并可大致以咱们想要的方式,修复那些有着明确纠正问题方法的一些编译器告警。咱们之前大概率已经见到过编译器告警了。比如,设想有下面这段代码: | ||
|
||
文件名:`src/main.rs` | ||
|
||
```rust | ||
fn do_something() {} | ||
|
||
fn main() { | ||
for i in 0..100 { | ||
do_something(); | ||
} | ||
} | ||
``` | ||
|
||
此处,咱们正调用 `do_something` 函数 100 次,但咱们在 `for` 循环的代码体中,从未用到那个变量 `i`。Rust 就会就此对咱们发出告警: | ||
|
||
```console | ||
$ cargo build | ||
Compiling rustfix_demo v0.1.0 (/home/lenny.peng/rust-lang-zh_CN/rustfix_demo) | ||
warning: unused variable: `i` | ||
--> src/main.rs:4:9 | ||
| | ||
4 | for i in 0..100 { | ||
| ^ help: if this is intentional, prefix it with an underscore: `_i` | ||
| | ||
= note: `#[warn(unused_variables)]` on by default | ||
|
||
warning: `rustfix_demo` (bin "rustfix_demo") generated 1 warning | ||
Finished dev [unoptimized + debuginfo] target(s) in 0.29s | ||
``` | ||
|
||
这个告警建议咱们要使用 `_i` 做名字:其中的下划线表示咱们有意不使用这个变量。通过运行 `cargo fix` 命令,咱们就可以使用 `rustfix`,自动应用那项建议: | ||
|
||
```console | ||
$ cargo fix --allow-no-vcs | ||
Checking rustfix_demo v0.1.0 (/home/lenny.peng/rust-lang-zh_CN/rustfix_demo) | ||
Fixed src/main.rs (1 fix) | ||
Finished dev [unoptimized + debuginfo] target(s) in 0.17s | ||
``` | ||
|
||
当咱们再次看到 `src/main.rs`,就将发现 `cargo fix` 已修改了这段代码: | ||
|
||
文件名:`src/main.rs` | ||
|
||
```rust | ||
fn do_something() {} | ||
|
||
fn main() { | ||
for _i in 0..100 { | ||
do_something(); | ||
} | ||
} | ||
``` | ||
|
||
那个 `for` 循环变量,现在就被命名为了 `_i`,同时那条告警也不再出现了。 | ||
|
||
咱们还可使用 `cargo fix` 命令,将咱们的代码在不同 Rust 版本之间转换。有关这些 Rust 版本,在附录 E 中有讲到。 | ||
|
||
|
||
## 使用 Clippy 获得更多的代码静态分析 | ||
|
||
**More Lints with Clippy** | ||
|
||
Clippy 工具是用于分析咱们代码,从而咱们可以捕获到一些常见错误,而改进咱们 Rust 代码的一套代码静态分析集合。 | ||
|
||
要安装 Clippy,请输入以下命令: | ||
|
||
```console | ||
$ rustup component add Clippy | ||
``` | ||
|
||
在任何 Cargo 项目上要运行 Clippy 的静态分析,请输入以下命令: | ||
|
||
```console | ||
$ cargo clippy | ||
``` | ||
|
||
比如说咱们编写了像下面这个程序这样,用到某个数学常量近似值,好比说 `pi`,的一个程序: | ||
|
||
文件名:`src/main.rs` | ||
|
||
```rust | ||
fn main() { | ||
let x = 3.1415; | ||
let r = 8.0; | ||
println!("圆的面积为 {}", x * r * r); | ||
} | ||
``` | ||
|
||
在这个项目上运行 `cargo clippy` 就会得到下面的报错: | ||
|
||
```console | ||
$ cargo clippy | ||
Checking clippy_demo v0.1.0 (/home/lenny.peng/rust-lang-zh_CN/clippy_demo) | ||
error: approximate value of `f{32, 64}::consts::PI` found | ||
--> src/main.rs:2:13 | ||
| | ||
2 | let x = 3.1415; | ||
| ^^^^^^ | ||
| | ||
= help: consider using the constant directly | ||
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant | ||
= note: `#[deny(clippy::approx_constant)]` on by default | ||
|
||
error: could not compile `clippy_demo` due to previous error | ||
``` | ||
|
||
此报错让咱们明白,Rust 已经定义了一个更精确的 `PI` 常量,且当咱们使用这个常量时,咱们的程序将更为正确。那么咱们随后就应修改咱们代码为使用这个 `PI` 常量。下面的代码就捕获导致 Clippy 的任何错误或告警: | ||
|
||
文件名:`src/main.rs` | ||
|
||
```rust | ||
fn main() { | ||
let x = std::f64::consts::PI; | ||
let r = 8.0; | ||
println!("圆的面积为 {}", x * r * r); | ||
} | ||
``` | ||
|
||
有关 Clippy 的更多信息,请参阅 [其文档](https://github.com/rust-lang/rust-clippy)。 | ||
|
||
|
||
## 用到 `rust-analyzer` 的 IDE 集成 | ||
|
||
**IDE Integration Using `rust-analyzer`** | ||
|
||
|
||
为帮助 IDE 集成,Rust 社区建议使用 [`rust-analyzer`](https://rust-analyzer.github.io/)。此工具是一套以编译器为中心,操 [语言服务器协议,Language Server Protocol](http://langserver.org/) 的实用工具;而所谓语言服务器协议,则是用于各种 IDEs 和编程语言,二者相互之间通信的一种规格。有多种不同客户端可使用 `rust-analyzer`,比如 [Visual Studio Code 的 Rust 分析器插件](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer)。 | ||
|
||
请访问 `rust-analyzer` 项目 [主页](https://rust-analyzer.github.io/),了解其安全说明,随后在咱们的特定 IDE 中安装该语言的服务器支持。咱们的 IDE 就能获得诸如自动补全、跳至定义及行内报错等能力。 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
# 附录 E:关于版本 | ||
|
||
**Appendix E - Editions** | ||
|
||
在第一章中,咱们曾看到 `cargo new` 会把一点有关某个版的元数据,添加到咱们的 `Cargo.toml` 文件。此附录就会讲到那意味着什么! | ||
|
||
Rust 语言及编译器有着六周的发布周期,意味着用户会得到源源不断的新功能。其他编程语言会不经常地发布较大变更;Rust 则会更频繁发布较小的更新。不久之后,全部这些小修改就会堆积起来。不过这一个个发布中,回头看看而讲到,“噢,从版本 1.10 到 1.31,Rust 改变了很多!”。则是不容易的。 | ||
|
||
每两三年,Rust 团队都会产生一个新的 Rust *版本,edition*。每个版本都会以完全更新的文档与工具,将那些业已落地到一个明确包中的特性放到一起。新版本会作为寻常的六周发布过程而交付。 | ||
|
||
这些版本服务了不同人群的不同目的: | ||
|
||
- 对于活跃的 Rust 用户,新版本会把那些增量变更,一起放入到一个易于掌握的包中; | ||
- 对于那些非用户,新版本释放了一些已落地的大进展信号,这会让 Rust 或许值得再看一看; | ||
- 对于开发 Rust 的人们,新版本会提供这个项目作为整体的集结点。 | ||
|
||
在本书编写时,已有三个 Rust 版本可用:Rust 2015、Rust 2018 与 Rust 2021。本书是用 Rust 2021 版本的习惯用语编写的。 | ||
|
||
`Cargo.toml` 中的 `edition` 键,表示应对咱们的代码使用哪个版本的编译器。若该键不存在,Rust 就会以向后兼容原因,而使用 `2015` 作为版本值。 | ||
|
||
每个项目都可以选择一个不同于默认 2015 的版本。这些版本可能包含了不兼容的变更,比如包含了与代码中标识符冲突的新关键字。但是,除非咱们选到这些变更,那么即使咱们更新了所使用的 Rust 编译器,咱们的代码将继续编译。 | ||
|
||
全部 Rust 编译器版本,都会支持先于那个编译器发布而存在的任何版本,且他们可将任何受支持版本的代码箱连接起来。版本变更只会影响编译器于编译初期解析代码的方式。因此,当咱们正使用着 Rust 2015,而咱们的一项依赖使用了 Rust 2018 时,咱们的项目将编译,并能够使用那项依赖。与之相反,在咱们的项目使用 Rust 2018,而一项依赖使用了 Rust 2015 的情形下,也会工作。 | ||
|
||
要明确的是:绝大多数特性,在所有版本上都将可用。使用任何 Rust 版本的开发者,都将在新的稳定发布构造出来时,发现一些改进。但是,在一些情况下,主要是在新曾了关键字时,一些新特性就会只在稍后版本中可用了。若咱们打算利用上这些新特性,咱们将需要切换版本。 | ||
|
||
有关更多细节,[版本指南,Edition Guide](https://doc.rust-lang.org/stable/edition-guide/) 是本列举了不同版本间差异,并解释了怎样通过 `cargo fix`,而自动将咱们的代码更新到新版的一本完整的书。 |
Oops, something went wrong.