check to appx-07

This commit is contained in:
KaiserY 2019-12-22 10:57:37 +08:00
parent 165c7283a1
commit 28d4b315e6
15 changed files with 200 additions and 228 deletions

View File

@ -2,11 +2,13 @@
> [appendix-01-keywords.md](https://raw.githubusercontent.com/rust-lang/book/master/src/appendix-01-keywords.md)
> <br>
> commit 0e9061cbaf95adfb9f3ed36c6cef4c046f282e86
> commit 27dd97a785794709aa87c51ab697cded41e8163a
下面的列表包含 Rust 中正在使用或者以后会用到的关键字。因此,这些关键字不能被用作标识符(除了 [原始标识符][raw-identifiers]这包括函数、变量、参数、结构体字段、模块、crate、常量、宏、静态值、属性、类型、trait 或生命周期
下面的列表包含 Rust 中正在使用或者以后会用到的关键字。因此,这些关键字不能被用作标识符(除了 [原始标识符][raw-identifiers]” 部分介绍的原始标识符这包括函数、变量、参数、结构体字段、模块、crate、常量、宏、静态值、属性、类型、trait 或生命周期
的名字。
[raw-identifiers]: #raw-identifiers
### 目前正在使用的关键字
如下关键字目前有对应其描述的功能。
@ -54,6 +56,7 @@
* `abstract`
* `async`
* `await`
* `become`
* `box`
* `do`
@ -68,13 +71,12 @@
* `yield`
### 原始标识符
[raw-identifiers]: #raw-identifiers
原始标识符Raw identifiers允许你使用通常不能使用的关键字其带有 `r#` 前缀。
例如,`match` 是关键字。如果尝试编译这个函数:
例如,`match` 是关键字。如果尝试编译如下使用 `match` 作为名字的函数:
```rust,ignore
```rust,ignore,does_not_compile
fn match(needle: &str, haystack: &str) -> bool {
haystack.contains(needle)
}
@ -90,7 +92,9 @@ error: expected identifier, found keyword `match`
| ^^^^^ expected identifier, found keyword
```
该错误表示你不能将关键字`match`用作函数标识符。 你可以使用原始标识符将`match`作为函数名称使用:
该错误表示你不能将关键字 `match` 用作函数标识符。你可以使用原始标识符将 `match` 作为函数名称使用:
<span class="filename">文件名: src/main.rs</span>
```rust
fn r#match(needle: &str, haystack: &str) -> bool {
@ -102,6 +106,8 @@ fn main() {
}
```
此代码编译没有任何错误。注意 `r#` 前缀需同时用于函数名和调用。
此代码编译没有任何错误。注意 `r#` 前缀需同时用于函数名定义 `main` 函数中的调用。
原始标识符允许使用你选择的任何单词作为标识符,即使该单词恰好是保留关键字。 此外原始标识符允许你使用以不同于你的crate使用的Rust版本编写的库。比如`try` 在 2015 edition 中不是关键字,而在 2018 edition 则是。所以如果如果用 2015 edition 编写的库中带有 `try` 函数,在 2018 edition 中调用时就需要使用原始标识符。有关版本的更多信息,请参见[附录E](https://github.com/KaiserY/trpl-zh-cn/blob/master/src/appendix-05-editions.md).
原始标识符允许使用你选择的任何单词作为标识符,即使该单词恰好是保留关键字。 此外,原始标识符允许你使用以不同于你的 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

@ -2,7 +2,7 @@
> [appendix-02-operators.md](https://github.com/rust-lang/book/blob/master/src/appendix-02-operators.md)
> <br />
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
> commit 426f3e4ec17e539ae9905ba559411169d303a031
该附录包含了 Rust 语法的词汇表包括运算符以及其他的符号这些符号单独出现或出现在路径、泛型、trait bounds、宏、属性、注释、元组以及大括号上下文中。
@ -35,7 +35,7 @@
| `-` | `- expr` | 算术取负 | `Neg` |
| `-` | `expr - expr` | 算术减法| `Sub` |
| `-=` | `var -= expr` | 算术减法与赋值 | `SubAssign` |
| `->` | `fn(...) -> type`, <code>\|...\| -> type</code> | 函数与闭包,返回类型 | |
| `->` | `fn(...) -> type`, <code>&vert;...&vert; -> type</code> | 函数与闭包,返回类型 | |
| `.` | `expr.ident` | 成员访问 | |
| `..` | `..`, `expr..`, `..expr`, `expr..expr` | 右排除范围 | |
| `..` | `..expr` | 结构体更新语法 | |
@ -62,10 +62,10 @@
| `@` | `ident @ pat` | 模式绑定 | |
| `^` | `expr ^ expr` | 按位异或 | `BitXor` |
| `^=` | `var ^= expr` | 按位异或与赋值 | `BitXorAssign` |
| <code>\|</code> | <code>pat \| pat</code> | 模式选择 | |
| <code>\|</code> | <code>expr \| expr</code> | 按位或 | `BitOr` |
| <code>\|=</code> | <code>var \|= expr</code> | 按位或与赋值 | `BitOrAssign` |
| <code>\|\|</code> | <code>expr \|\| expr</code> | 逻辑或 | |
| <code>&vert;</code> | <code>pat &vert; pat</code> | 模式选择 | |
| <code>&vert;</code> | <code>expr &vert; expr</code> | 按位或 | `BitOr` |
| <code>&vert;=</code> | <code>var &vert;= expr</code> | 按位或与赋值 | `BitOrAssign` |
| <code>&vert;&vert;</code> | <code>expr &vert;&vert; expr</code> | 逻辑或 | |
| `?` | `expr?` | 错误传播 | |
### 非运算符符号
@ -86,7 +86,7 @@
| `br"..."`, `br#"..."#`, `br##"..."##`, 等 | 原始字节字符串字面值,原始和字节字符串字面值的结合 |
| `'...'` | 字符字面值 |
| `b'...'` | ASCII 码字节字面值 |
| <code>\|...\| expr</code> | 闭包 |
| <code>&vert;...&vert; expr</code> | 闭包 |
| `!` | 离散函数的总是为空的类型 |
| `_` | “忽略” 模式绑定;也用于增强整型字面值的可读性 |

View File

@ -2,7 +2,7 @@
> [appendix-03-derivable-traits.md](https://github.com/rust-lang/book/blob/master/src/appendix-03-derivable-traits.md)
> <br />
> commit a86c1d315789b3ca13b20d50ad5005c62bdd9e37
> commit 426f3e4ec17e539ae9905ba559411169d303a031
在本书的各个部分中,我们讨论了可应用于结构体和枚举定义的 `derive` 属性。`derive` 属性会在使用 `derive` 语法标记的类型上生成对应 trait 的默认实现的代码。
@ -20,7 +20,7 @@
一个无法被派生的 trait 的例子是为终端用户处理格式化的 `Display` 。你应该时常考虑使用合适的方法来为终端用户显示一个类型。终端用户应该看到类型的什么部分他们会找出相关部分吗对他们来说最相关的数据格式是什么样的Rust 编译器没有这样的洞察力,因此无法为你提供合适的默认行为。
本附录所提供的可派生 trait 列表并不全面:库可以为其自己的 trait 实现 `derive`,可以使用 `derive` 的 trait 列表事实上是无限的。实现 `derive` 涉及到过程宏的应用,这在第十九章的 “宏” 有介绍。
本附录所提供的可派生 trait 列表并不全面:库可以为其自己的 trait 实现 `derive`,可以使用 `derive` 的 trait 列表事实上是无限的。实现 `derive` 涉及到过程宏的应用,这在第十九章的 [“宏”][macros] 有介绍。
### 用于程序员输出的 `Debug`
@ -58,13 +58,13 @@
## 复制值的 `Clone``Copy`
`Clone` trait 可以明确地创建一个值的深拷贝deep copy复制过程可能包含任意代码的执行以及堆上数据的复制。查阅第四章 “变量和数据的交互方式:移动” 以获取有关 `Clone` 的更多信息。
`Clone` trait 可以明确地创建一个值的深拷贝deep copy复制过程可能包含任意代码的执行以及堆上数据的复制。查阅第四章 [“变量和数据的交互方式:移动”][ways-variables-and-data-interact-clone] 以获取有关 `Clone` 的更多信息。
派生 `Clone` 实现了 `clone` 方法,其为整个的类型实现时,在类型的每一部分上调用了 `clone` 方法。这意味着类型中所有字段或值也必须实现 `Clone` 来派生 `Clone`
例如,当在一个切片上调用 `to_vec` 方法时,`Clone` 是必须的。切片并不拥有其所包含实例的类型,但是从 `to_vec` 中返回的 vector 需要拥有其实例,因此,`to_vec` 在每个元素上调用 `clone`。因此,存储在切片中的类型必须实现 `Clone`
`Copy` trait 允许你通过只拷贝存储在栈上的位来复制值而不需要其他代码。查阅第四章“只在栈上的数据:拷贝”的部分来获取有关 `Copy` 的更多信息。
`Copy` trait 允许你通过只拷贝存储在栈上的位来复制值而不需要其他代码。查阅第四章 [“只在栈上的数据:拷贝”][stack-only-data-copy] 的部分来获取有关 `Copy` 的更多信息。
`Copy` trait 并未定义任何方法来阻止编程人员重写这些方法或违反无代码可执行的假设。所以,所有的编程人员可以假设复制一个值非常快。
@ -72,7 +72,7 @@
`Copy` trait 很少使用;实现 `Copy` 的类型是可以优化的,这意味着你无需调用 `clone`,这让代码更简洁。
使用 `Clone` 实现 `Copy` 也是有可能的,但代码可能会稍慢或是要使用 `clone` 替代。
任何使用 `Copy` 的代码都可以通过 `Clone` 实现,但代码可能会稍慢或是要使用 `clone` 替代。
## 固定大小的值到值映射的 `Hash`
@ -84,6 +84,14 @@
`Default` trait 使你创建一个类型的默认值。 派生 `Default` 实现了 `default` 函数。`default` 函数的派生实现调用了类型每部分的 `default` 函数,这意味着类型中所有的字段或值也必须把 `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` 的结果。
[creating-instances-from-other-instances-with-struct-update-syntax]:
ch05-01-defining-structs.html#creating-instances-from-other-instances-with-struct-update-syntax
[stack-only-data-copy]:
ch04-01-what-is-ownership.html#stack-only-data-copy
[ways-variables-and-data-interact-clone]:
ch04-01-what-is-ownership.html#ways-variables-and-data-interact-clone
[macros]: ch19-06-macros.html#macros

View File

@ -2,7 +2,7 @@
> [appendix-04-useful-development-tools.md](https://github.com/rust-lang/book/blob/master/src/appendix-04-useful-development-tools.md)
> <br />
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
> commit 70a82519e48b8a61f98cabb8ff443d1b21962fea
本附录,我们将讨论 Rust 项目提供的用于开发 Rust 代码的工具。
@ -10,12 +10,10 @@
`rustfmt` 工具根据社区代码风格格式化代码。很多项目使用 `rustfmt` 来避免编写 Rust 风格的争论:所有人都用这个工具格式化代码!
`rustfmt` 工具的质量还未达到发布 1.0 版本的水平,不过目前有一个可用的预览版。请尝试使用并告诉我们它如何!
安装 `rustfmt`
```text
$ rustup component add rustfmt-preview
$ rustup component add rustfmt
```
这会提供 `rustfmt``cargo-fmt`,类似于 Rust 同时安装 `rustc``cargo`。为了格式化整个 Cargo 项目:
@ -91,12 +89,10 @@ fn main() {
`clippy` 工具是一系列 lint 的集合,用于捕捉常见错误和改进 Rust 代码。
`clippy` 工具的质量还未达到发布 1.0 版本的水平,不过目前有一个可用的预览版。请尝试使用并告诉我们它如何!
安装 `clippy`
```text
$ rustup component add clippy-preview
$ rustup component add clippy
```
对任何 Cargo 项目运行 clippy 的 lint
@ -127,7 +123,7 @@ error: approximate value of `f{32, 64}::consts::PI` found. Consider using it dir
| ^^^^^^
|
= note: #[deny(clippy::approx_constant)] on by default
= help: for further information visit https://rust-lang-nursery.github.io/rust-clippy/v0.0.212/index.html#approx_constant
= help: for further information visit https://rust-lang-nursery.github.io/rust-clippy/master/index.html#approx_constant
```
这告诉我们 Rust 定义了更为精确的常量,而如果使用了这些常量程序将更加准确。如下代码就不会导致 `clippy` 产生任何错误或警告:
@ -144,7 +140,7 @@ fn main() {
请查看 [其文档][clippy] 来了解 `clippy` 的更多信息。
[clippy]: https://github.com/rust-lang-nursery/rust-clippy
[clippy]: https://github.com/rust-lang/rust-clippy
## 使用 Rust Language Server 的 IDE 集成
@ -158,11 +154,11 @@ fn main() {
安装 `rls`
```text
$ rustup component add rls-preview
$ rustup component add rls
```
接着为特定的 IDE 安装 language server 支持,如比便会获得如自动补全、跳转到定义和 inline error 之类的功能。
请查看 [其文档][rls] 来了解 `rls` 的更多信息。
[rls]: https://github.com/rust-lang-nursery/rls
[rls]: https://github.com/rust-lang/rls

View File

@ -2,7 +2,7 @@
> [appendix-05-editions.md](https://github.com/rust-lang/book/blob/master/src/appendix-05-editions.md)
> <br />
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
> commit 70a82519e48b8a61f98cabb8ff443d1b21962fea
早在第一章,我们见过 `cargo new`*Cargo.toml* 中增加了一些有关 `edition` 的元数据。本附录将解释其意义!
@ -20,7 +20,9 @@ Rust 语言和编译器有一个为期 6 周的发布循环。这意味着用户
*Cargo.toml* 中的 `edition` 字段表明代码应该使用哪个版本编译。如果该字段不存在,其默认为 `2015` 以提供后向兼容性。
每个项目都可以选择不同于默认的 2015 edition 的版本。这样,版本可能会包含不兼容的修改,比如新增关键字可能会与代码中的标识符冲突并导致错误。不过除非选择兼容这些修改,(旧)代码仍将能够编译,即便升级了 Rust 编译器的版本。所有 Rust 编译器都支持任何之前存在的编译器版本,并可以链接人恶化支持版本的 crate。编译器修改只影响最初的解析代码的过程。因此如果你使用 Rust 2015 而某个依赖使用 Rust 2018你的项目仍旧能够编译并使用该依赖。反之若项目使用 Rust 2018 而依赖使用 Rust 2015 亦可工作。
每个项目都可以选择不同于默认的 2015 edition 的版本。这样,版本可能会包含不兼容的修改,比如新增关键字可能会与代码中的标识符冲突并导致错误。不过除非选择兼容这些修改,(旧)代码仍将能够编译,即便升级了 Rust 编译器的版本。
所有 Rust 编译器都支持任何之前存在的编译器版本,并可以链接人恶化支持版本的 crate。编译器修改只影响最初的解析代码的过程。因此如果你使用 Rust 2015 而某个依赖使用 Rust 2018你的项目仍旧能够编译并使用该依赖。反之若项目使用 Rust 2018 而依赖使用 Rust 2015 亦可工作。
有一点需要明确:大部分功能在所有版本中都能使用。开发者使用任何 Rust 版本将能继续接收最新稳定版的改进。然而在一些情况,主要是增加了新关键字的时候,则可能出现了只能用于新版本的功能。只需切换版本即可利用新版本的功能。

View File

@ -2,7 +2,7 @@
> [appendix-06-translation.md](https://github.com/rust-lang/book/blob/master/src/appendix-06-translation.md)
> <br />
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
> commit 72900e05f04ae60e06c2665567771bdd8befa89c
一些非英语语言的资源。多数仍在翻译中;查阅 [翻译标签][label] 来帮助我们或使我们知道新的翻译!
@ -10,12 +10,11 @@
- [Português](https://github.com/rust-br/rust-book-pt-br) (BR)
- [Português](https://github.com/nunojesus/rust-book-pt-pt) (PT)
- [Tiếng việt](https://github.com/hngnaig/rust-lang-book/tree/vi-VN)
- [简体中文](http://www.broadview.com.cn/article/144), [另一个版本(译者注:已基本翻译完毕)](https://github.com/KaiserY/trpl-zh-cn)
- [简体中文](https://github.com/KaiserY/trpl-zh-cn)
- [Українська](https://github.com/pavloslav/rust-book-uk-ua)
- [Español](https://github.com/thecodix/book)
- [Español](https://github.com/thecodix/book), [alternate](https://github.com/ManRR/rust-book-es)
- [Italiano](https://github.com/AgeOfWar/rust-book-it)
- [Русский](https://github.com/iDeBugger/rust-book-ru)
- [Русский](https://github.com/ruRust/rust_book_2ed)
- [한국어](https://github.com/rinthel/rust-lang-book-ko)
- [日本語](https://github.com/hazama-yuinyan/book)
- [Français](https://github.com/quadrifoglio/rust-book-fr)
@ -23,3 +22,6 @@
- [עברית](https://github.com/idanmel/rust-book-heb)
- [Cebuano](https://github.com/agentzero1/book)
- [Tagalog](https://github.com/josephace135/book)
- [Esperanto](https://github.com/psychoslave/Rust-libro)
- [ελληνική](https://github.com/TChatzigiannakis/rust-book-greek)
- [Svenska](https://github.com/sebras/book)

View File

@ -2,7 +2,7 @@
> [appendix-07-nightly-rust.md](https://github.com/rust-lang/book/blob/master/src/appendix-07-nightly-rust.md)
> <br />
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
> commit 70a82519e48b8a61f98cabb8ff443d1b21962fea
本附录介绍 Rust 是如何开发的以及这如何影响作为 Rust 开发者的你。
@ -14,11 +14,11 @@
### Choo, Choo!开车啦发布通道和发布时刻表Riding the Trains
Rust 开发运行于一个 **发布时刻表***train schedule*)之上。也就是说,所有的开发工作都位于 Rust 仓库的 `master` 分支。发布采用 software release train 模型,其被用于 思科 IOS 等其它软件项目。Rust 有三个 **发布通道***release channel*
Rust 开发运行于一个 **发布时刻表***train schedule*)之上。也就是说,所有的开发工作都位于 Rust 仓库的 `master` 分支。发布采用 software release train 模型,其被用于思科 IOS 等其它软件项目。Rust 有三个 **发布通道***release channel*
* Nightly
* Beta
* Stable (稳定版)
* Stable稳定版
大部分 Rust 开发者主要采用稳定版通道,不过希望实验新功能的开发者可能会使用 nightly 或 beta 版。
@ -44,6 +44,14 @@ nightly: * - - * - - * - - * - - *
beta: *
```
比如我们发现了一个回归缺陷。好消息是在这些缺陷流入稳定发布之前还有一些时间来测试 beta 版本fix 被合并到 `master`,为此 nightly 爸爸嫩得到了修复,接着这些 fix 将 backport 到 `beta` 分支,一个新的 beta 发布就产生了:
```text
nightly: * - - * - - * - - * - - * - - *
|
beta: * - - - - - - - - *
```
第一个 beta 版的 6 周后,是发布稳定版的时候了!`stable` 分支从 `beta` 分支生成:
```text
@ -108,7 +116,7 @@ $ rustup override set nightly
那么你如何了解这些新功能呢Rust 开发模式遵循一个 **Request For Comments (RFC) 过程**。如果你希望改进 Rust可以编写一个提议也就是 RFC。
任何人都可以编写 RFC 来改进 Rust同时这些 RFC 会被 Rust 团队评审和讨论,他们由很多不同分工的子团队组成。这里是 [Rust 官网上](https://www.rust-lang.org/en-US/team.html) 所有团队的总列表,其包含了项目中每个领域的团队:语言设计、编译器实现、基础设施、文档等。个个团队会阅读相应的提议和评论,编写回复,并最终达成接受或回绝功能的一致。
任何人都可以编写 RFC 来改进 Rust同时这些 RFC 会被 Rust 团队评审和讨论,他们由很多不同分工的子团队组成。这里是 [Rust 官网上](https://www.rust-lang.org/governance) 所有团队的总列表,其包含了项目中每个领域的团队:语言设计、编译器实现、基础设施、文档等。个个团队会阅读相应的提议和评论,编写回复,并最终达成接受或回绝功能的一致。
如果功能被接受了,在 Rust 仓库会打开一个 issue人们就可以实现它。实现功能的人当人可能不是最初提议功能的人当实现完成后其会合并到 `master` 分支并位于一个功能开关feature gate之后正如 [“不稳定功能”](#unstable-features) 部分所讨论的。

View File

@ -2,9 +2,9 @@
> [ch19-03-advanced-traits.md](https://github.com/rust-lang/book/blob/master/src/ch19-03-advanced-traits.md)
> <br>
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
> commit 426f3e4ec17e539ae9905ba559411169d303a031
第十章 “trait定义共享的行为” 部分,我们第一次涉及到了 trait不过就像生命周期一样我们并没有覆盖一些较为高级的细节。现在我们更加了解 Rust 了,可以深入理解其本质了。
第十章 [“trait定义共享的行为”][traits-defining-shared-behavior] 部分,我们第一次涉及到了 trait不过就像生命周期一样我们并没有覆盖一些较为高级的细节。现在我们更加了解 Rust 了,可以深入理解其本质了。
### 关联类型在 trait 定义中指定占位符类型
@ -12,7 +12,7 @@
本章所描述的大部分内容都非常少见。关联类型则比较适中;它们比本书其他的内容要少见,不过比本章中的很多内容要更常见。
一个带有关联类型的 trait 的例子是标准库提供的 `Iterator` trait。它有一个叫做 `Item` 的关联类型来替代遍历的值的类型。第十三章的 “`Iterator` trait 和 `next` 方法” 部分曾提到过 `Iterator` trait 的定义如示例 19-20 所示:
一个带有关联类型的 trait 的例子是标准库提供的 `Iterator` trait。它有一个叫做 `Item` 的关联类型来替代遍历的值的类型。第十三章的 [“`Iterator` trait 和 `next` 方法”][the-iterator-trait-and-the-next-method] 部分曾提到过 `Iterator` trait 的定义如示例 19-12 所示:
```rust
pub trait Iterator {
@ -22,7 +22,7 @@ pub trait Iterator {
}
```
<span class="caption">示例 19-20: `Iterator` trait 的定义中带有关联类型 `Item`</span>
<span class="caption">示例 19-12: `Iterator` trait 的定义中带有关联类型 `Item`</span>
`Item` 是一个占位类型,同时 `next` 方法定义表明它返回 `Option<Self::Item>` 类型的值。这个 trait 的实现者会指定 `Item` 的具体类型,然而不管实现者指定何种类型, `next` 方法都会返回一个包含了此具体类型值的 `Option`
@ -40,7 +40,7 @@ impl Iterator for Counter {
// --snip--
```
这类似于泛型。那么为什么 `Iterator` trait 不像示例 19-21 那样定义呢?
这类似于泛型。那么为什么 `Iterator` trait 不像示例 19-13 那样定义呢?
```rust
pub trait Iterator<T> {
@ -50,9 +50,9 @@ pub trait Iterator<T> {
<span class="caption">示例 19-21: 一个使用泛型的 `Iterator` trait 假想定义</span>
区别在于当如示例 19-21 那样使用泛型时,则不得不在每一个实现中标注类型。这是因为我们也可以实现为 `Iterator<String> for Counter`,或任何其他类型,这样就可以有多个 `Counter``Iterator` 的实现。换句话说,当 trait 有泛型参数时,可以多次实现这个 trait每次需改变泛型参数的具体类型。接着当使用 `Counter``next` 方法时,必须提供类型注解来表明希望使用 `Iterator` 的哪一个实现。
区别在于当如示例 19-13 那样使用泛型时,则不得不在每一个实现中标注类型。这是因为我们也可以实现为 `Iterator<String> for Counter`,或任何其他类型,这样就可以有多个 `Counter``Iterator` 的实现。换句话说,当 trait 有泛型参数时,可以多次实现这个 trait每次需改变泛型参数的具体类型。接着当使用 `Counter``next` 方法时,必须提供类型注解来表明希望使用 `Iterator` 的哪一个实现。
通过关联类型,则无需标注类型因为不能多次实现这个 trait。对于示例 19-20 使用关联类型的定义,我们只能选择一次 `Item` 会是什么类型,因为只能有一个 `impl Iterator for Counter`。当调用 `Counter``next` 时不必每次指定我们需要 `u32` 值的迭代器。
通过关联类型,则无需标注类型因为不能多次实现这个 trait。对于示例 19-12 使用关联类型的定义,我们只能选择一次 `Item` 会是什么类型,因为只能有一个 `impl Iterator for Counter`。当调用 `Counter``next` 时不必每次指定我们需要 `u32` 值的迭代器。
### 默认泛型类型参数和运算符重载
@ -60,7 +60,7 @@ pub trait Iterator<T> {
这种情况的一个非常好的例子是用于运算符重载。**运算符重载***Operator overloading*)是指在特定情况下自定义运算符(比如 `+`)行为的操作。
Rust 并不允许创建自定义运算符或重载任意运算符,不过 `std::ops` 中所列出的运算符和相应的 trait 可以通过实现运算符相关 trait 来重载。例如,示例 19-22 中展示了如何在 `Point` 结构体上实现 `Add` trait 来重载 `+` 运算符,这样就可以将两个 `Point` 实例相加了:
Rust 并不允许创建自定义运算符或重载任意运算符,不过 `std::ops` 中所列出的运算符和相应的 trait 可以通过实现运算符相关 trait 来重载。例如,示例 19-14 中展示了如何在 `Point` 结构体上实现 `Add` trait 来重载 `+` 运算符,这样就可以将两个 `Point` 实例相加了:
<span class="filename">文件名: src/main.rs</span>
@ -90,7 +90,7 @@ fn main() {
}
```
<span class="caption">示例 19-22: 实现 `Add` trait 重载 `Point` 实例的 `+` 运算符</span>
<span class="caption">示例 19-14: 实现 `Add` trait 重载 `Point` 实例的 `+` 运算符</span>
`add` 方法将两个 `Point` 实例的 `x` 值和 `y` 值分别相加来创建一个新的 `Point`。`Add` trait 有一个叫做 `Output` 的关联类型,它用来决定 `add` 方法的返回值类型。
@ -108,7 +108,7 @@ trait Add<RHS=Self> {
当为 `Point` 实现 `Add` 时,使用了默认的 `RHS`,因为我们希望将两个 `Point` 实例相加。让我们看看一个实现 `Add` trait 时希望自定义 `RHS` 类型而不是使用默认类型的例子
这里有两个存放不同单元值的结构体,`Millimeters` 和 `Meters`。我们希望能够将毫米值与米值相加,并让 `Add` 的实现正确处理转换。可以为 `Millimeters` 实现 `Add` 并以 `Meters` 作为 `RHS`,如示例 19-23 所示。
这里有两个存放不同单元值的结构体,`Millimeters` 和 `Meters`。我们希望能够将毫米值与米值相加,并让 `Add` 的实现正确处理转换。可以为 `Millimeters` 实现 `Add` 并以 `Meters` 作为 `RHS`,如示例 19-15 所示。
<span class="filename">文件名: src/lib.rs</span>
@ -127,7 +127,7 @@ impl Add<Meters> for Millimeters {
}
```
<span class="caption">示例 19-23: 在 `Millimeters` 上实现 `Add`,以便能够将 `Millimeters``Meters` 相加</span>
<span class="caption">示例 19-15: 在 `Millimeters` 上实现 `Add`,以便能够将 `Millimeters``Meters` 相加</span>
为了使 `Millimeters``Meters` 能够相加,我们指定 `impl Add<Meters>` 来设定 `RHS` 类型参数的值而不是使用默认的 `Self`
@ -144,7 +144,7 @@ impl Add<Meters> for Millimeters {
Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法,也不能阻止为同一类型同时实现这两个 trait。甚至直接在类型上实现开始已经有的同名方法也是可能的
不过,当调用这些同名方法时,需要告诉 Rust 我们希望使用哪一个。考虑一下示例 19-24 中的代码,这里定义了 trait `Pilot``Wizard` 都拥有方法 `fly`。接着在一个本身已经实现了名为 `fly` 方法的类型 `Human` 上实现这两个 trait。每一个 `fly` 方法都进行了不同的操作:
不过,当调用这些同名方法时,需要告诉 Rust 我们希望使用哪一个。考虑一下示例 19-16 中的代码,这里定义了 trait `Pilot``Wizard` 都拥有方法 `fly`。接着在一个本身已经实现了名为 `fly` 方法的类型 `Human` 上实现这两个 trait。每一个 `fly` 方法都进行了不同的操作:
<span class="filename">文件名: src/main.rs</span>
@ -178,9 +178,9 @@ impl Human {
}
```
<span class="caption">示例 19-24: 两个 trait 定义为拥有 `fly` 方法,并在直接定义有 `fly` 方法的 `Human` 类型上实现这两个 trait</span>
<span class="caption">示例 19-16: 两个 trait 定义为拥有 `fly` 方法,并在直接定义有 `fly` 方法的 `Human` 类型上实现这两个 trait</span>
当调用 `Human` 实例的 `fly` 时,编译器默认调用直接是现在类型上的方法,如示例 19-25 所示:
当调用 `Human` 实例的 `fly` 时,编译器默认调用直接是现在类型上的方法,如示例 19-17 所示。
<span class="filename">文件名: src/main.rs</span>
@ -219,11 +219,11 @@ fn main() {
}
```
<span class="caption">示例 19-25: 调用 `Human` 实例的 `fly`</span>
<span class="caption">示例 19-17: 调用 `Human` 实例的 `fly`</span>
运行这段代码会打印出 `*waving arms furiously*`,这表明 Rust 调用了直接实现在 `Human` 上的 `fly` 方法。
为了能够调用 `Pilot` trait 或 `Wizard` trait 的 `fly` 方法,我们需要使用更明显的语法以便能指定我们指的是哪个 `fly` 方法。这个语法展示在示例 19-26 中:
为了能够调用 `Pilot` trait 或 `Wizard` trait 的 `fly` 方法,我们需要使用更明显的语法以便能指定我们指的是哪个 `fly` 方法。这个语法展示在示例 19-18 中:
<span class="filename">文件名: src/main.rs</span>
@ -264,9 +264,9 @@ fn main() {
}
```
<span class="caption">示例 19-26: 指定我们希望调用哪一个 trait 的 `fly` 方法</span>
<span class="caption">示例 19-18: 指定我们希望调用哪一个 trait 的 `fly` 方法</span>
在方法名前指定 trait 名向 Rust 澄清了我们希望调用哪个 `fly` 实现。也可以选择写成 `Human::fly(&person)`,这等同于示例 19-26 中的 `person.fly()`,不过如果无需消歧义的话这么写就有点长了。
在方法名前指定 trait 名向 Rust 澄清了我们希望调用哪个 `fly` 实现。也可以选择写成 `Human::fly(&person)`,这等同于示例 19-18 中的 `person.fly()`,不过如果无需消歧义的话这么写就有点长了。
运行这段代码会打印出:
@ -278,7 +278,7 @@ Up!
因为 `fly` 方法获取一个 `self` 参数,如果有两个 **类型** 都实现了同一 **trait**Rust 可以根据 `self` 的类型计算出应该使用哪一个 trait 实现。
然而,关联函数是 trait 的一部分,但没有 `self` 参数。当同一作用域的两个类型实现了同一 traitRust 就不能计算出我们期望的是哪一个类型,除非使用 **完全限定语法***fully qualified syntax*)。例如,拿示例 19-27 中的 `Animal` trait 来说,它有关联函数 `baby_name`,结构体 `Dog` 实现了 `Animal`,同时有关联函数 `baby_name` 直接定义于 `Dog` 之上:
然而,关联函数是 trait 的一部分,但没有 `self` 参数。当同一作用域的两个类型实现了同一 traitRust 就不能计算出我们期望的是哪一个类型,除非使用 **完全限定语法***fully qualified syntax*)。例如,拿示例 19-19 中的 `Animal` trait 来说,它有关联函数 `baby_name`,结构体 `Dog` 实现了 `Animal`,同时有关联函数 `baby_name` 直接定义于 `Dog` 之上:
<span class="filename">文件名: src/main.rs</span>
@ -306,7 +306,7 @@ fn main() {
}
```
<span class="caption">示例 19-27: 一个带有关联函数的 trait 和一个带有同名关联函数并实现了此 trait 的类型</span>
<span class="caption">示例 19-19: 一个带有关联函数的 trait 和一个带有同名关联函数并实现了此 trait 的类型</span>
这段代码用于一个动物收容所,他们将所有的小狗起名为 Spot这实现为定义于 `Dog` 之上的关联函数 `baby_name`。`Dog` 类型还实现了 `Animal` trait它描述了所有动物的共有的特征。小狗被称为 puppy这表现为 `Dog``Animal` trait 实现中与 `Animal` trait 相关联的函数 `baby_name`
@ -316,7 +316,7 @@ fn main() {
A baby dog is called a Spot
```
这并不是我们需要的。我们希望调用的是 `Dog``Animal` trait 实现那部分的 `baby_name` 函数,这样能够打印出 `A baby dog is called a puppy`。示例 19-26 中用到的技术在这并不管用;如果将 `main` 改为示例 19-28 中的代码,则会得到一个编译错误:
这并不是我们需要的。我们希望调用的是 `Dog``Animal` trait 实现那部分的 `baby_name` 函数,这样能够打印出 `A baby dog is called a puppy`。示例 19-18 中用到的技术在这并不管用;如果将 `main` 改为示例 19-20 中的代码,则会得到一个编译错误:
<span class="filename">文件名: src/main.rs</span>
@ -326,7 +326,7 @@ fn main() {
}
```
<span class="caption">示例 19-28: 尝试调用 `Animal` trait 的 `baby_name` 函数,不过 Rust 并不知道该使用哪一个实现</span>
<span class="caption">示例 19-20: 尝试调用 `Animal` trait 的 `baby_name` 函数,不过 Rust 并不知道该使用哪一个实现</span>
因为 `Animal::baby_name` 是关联函数而不是方法,因此它没有 `self` 参数Rust 无法计算出所需的是哪一个 `Animal::baby_name` 实现。我们会得到这个编译错误:
@ -340,7 +340,7 @@ error[E0283]: type annotations required: cannot resolve `_: Animal`
= note: required by `Animal::baby_name`
```
为了消歧义并告诉 Rust 我们希望使用的是 `Dog``Animal` 实现,需要使用 **完全限定语法**,这是调用函数时最为明确的方式。示例 19-29 展示了如何使用完全限定语法:
为了消歧义并告诉 Rust 我们希望使用的是 `Dog``Animal` 实现,需要使用 **完全限定语法**,这是调用函数时最为明确的方式。示例 19-21 展示了如何使用完全限定语法:
<span class="filename">文件名: src/main.rs</span>
@ -368,7 +368,7 @@ fn main() {
}
```
<span class="caption">示例 19-29: 使用完全限定语法来指定我们希望调用的是 `Dog``Animal` trait 实现中的 `baby_name` 函数</span>
<span class="caption">示例 19-21: 使用完全限定语法来指定我们希望调用的是 `Dog``Animal` trait 实现中的 `baby_name` 函数</span>
我们在尖括号中向 Rust 提供了类型注解,并通过在此函数调用中将 `Dog` 类型当作 `Animal` 对待,来指定希望调用的是 `Dog``Animal` trait 实现中的 `baby_name` 函数。现在这段代码会打印出我们期望的数据:
@ -398,7 +398,7 @@ A baby dog is called a puppy
**********
```
`outline_print` 的实现中,因为希望能够使用 `Display` trait 的功能,则需要说明 `OutlinePrint` 只能用于同时也实现了 `Display` 并提供了 `OutlinePrint` 需要的功能的类型。可以通过在 trait 定义中指定 `OutlinePrint: Display` 来做到这一点。这类似于为 trait 增加 trait bound。示例 19-30 展示了一个 `OutlinePrint` 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>
@ -418,7 +418,7 @@ trait OutlinePrint: fmt::Display {
}
```
<span class="caption">示例 19-30: 实现 `OutlinePrint` trait它要求来自 `Display` 的功能</span>
<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`
@ -472,7 +472,7 @@ impl fmt::Display for Point {
### newtype 模式用以在外部类型上实现外部 trait
在第十章的 “为类型实现 trait” 部分我们提到了孤儿规则orphan rule它说明只要 trait 或类型对于当前 crate 是本地的话就可以在此类型上实现该 trait。一个绕开这个限制的方法是使用**newtype 模式***newtype pattern*),它涉及到在一个元组结构体(第五章 “用没有命名字段的元组结构体来创建不同的类型” 部分介绍了元组结构体)中创建一个新类型。这个元组结构体带有一个字段作为希望实现 trait 的类型的简单封装。接着这个封装类型对于 crate 是本地的,这样就可以在这个封装上实现 trait。“Newtype” 是一个源自U.C.0079Haskell 编程语言的概念。使用这个模式没有运行时性能惩罚,这个封装类型在编译时就被省略了。
在第十章的 [“为类型实现 trait”][implementing-a-trait-on-a-type] “为类型实现 trait” 部分我们提到了孤儿规则orphan rule它说明只要 trait 或类型对于当前 crate 是本地的话就可以在此类型上实现该 trait。一个绕开这个限制的方法是使用 **newtype 模式***newtype pattern*),它涉及到在一个元组结构体(第五章 [“用没有命名字段的元组结构体来创建不同的类型”][tuple-structs] 部分介绍了元组结构体)中创建一个新类型。这个元组结构体带有一个字段作为希望实现 trait 的类型的简单封装。接着这个封装类型对于 crate 是本地的,这样就可以在这个封装上实现 trait。*Newtype* 是一个源自U.C.0079Haskell 编程语言的概念。使用这个模式没有运行时性能惩罚,这个封装类型在编译时就被省略了。
例如,如果想要在 `Vec<T>` 上实现 `Display`,而孤儿规则阻止我们直接这么做,因为 `Display` trait 和 `Vec<T>` 都定义于我们的 crate 之外。可以创建一个包含 `Vec<T>` 实例的 `Wrapper` 结构体,接着可以如列表 19-31 那样在 `Wrapper` 上实现 `Display` 并使用 `Vec<T>` 的值:
@ -499,6 +499,15 @@ fn main() {
`Display` 的实现使用 `self.0` 来访问其内部的 `Vec<T>`,因为 `Wrapper` 是元组结构体而 `Vec<T>` 是结构体总位于索引 0 的项。接着就可以使用 `Wrapper``Display` 的功能了。
此方法的缺点是,因为 `Wrapper` 是一个新类型,它没有定义于其值之上的方法;必须直接在 `Wrapper` 上实现 `Vec<T>` 的所有方法,这样就可以代理到`self.0` 上 —— 这就允许我们完全像 `Vec<T>` 那样对待 `Wrapper`。如果希望新类型拥有其内部类型的每一个方法,为封装类型实现 `Deref` trait第十五章 “通过 `Deref` trait 将智能指针当作常规引用处理” 部分讨论过)并返回其内部类型是一种解决方案。如果不希望封装类型拥有所有内部类型的方法 —— 比如为了限制封装类型的行为 —— 则必须只自行实现所需的方法。
此方法的缺点是,因为 `Wrapper` 是一个新类型,它没有定义于其值之上的方法;必须直接在 `Wrapper` 上实现 `Vec<T>` 的所有方法,这样就可以代理到`self.0` 上 —— 这就允许我们完全像 `Vec<T>` 那样对待 `Wrapper`。如果希望新类型拥有其内部类型的每一个方法,为封装类型实现 `Deref` trait第十五章 [“通过 `Deref` trait 将智能指针当作常规引用处理”][smart-pointer-deref] 部分讨论过)并返回其内部类型是一种解决方案。如果不希望封装类型拥有所有内部类型的方法 —— 比如为了限制封装类型的行为 —— 则必须只自行实现所需的方法。
上面便是 newtype 模式如何与 trait 结合使用的;还有一个不涉及 trait 的实用模式。现在让我们将话题的焦点转移到一些与 Rust 类型系统交互的高级方法上来吧。
[implementing-a-trait-on-a-type]:
ch10-02-traits.html#implementing-a-trait-on-a-type
[the-iterator-trait-and-the-next-method]:
ch13-02-iterators.html#the-iterator-trait-and-the-next-method
[traits-defining-shared-behavior]:
ch10-02-traits.html#traits-defining-shared-behavior
[smart-pointer-deref]: ch15-02-deref.html#treating-smart-pointers-like-regular-references-with-the-deref-trait
[tuple-structs]: ch05-01-defining-structs.html#using-tuple-structs-without-named-fields-to-create-different-types

View File

@ -2,11 +2,11 @@
> [ch19-04-advanced-types.md](https://github.com/rust-lang/book/blob/master/src/ch19-04-advanced-types.md)
> <br>
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
> commit 426f3e4ec17e539ae9905ba559411169d303a031
Rust 的类型系统有一些我们曾经提到但没有讨论过的功能。首先我们从一个关于为什么 newtype 与类型一样有用的更宽泛的讨论开始。接着会转向类型别名type aliases一个类似于 newtype 但有着稍微不同的语义的功能。我们还会讨论 `!` 类型和动态大小类型。
> 这一部分假设你已经阅读了之前的 “newtype 模式用于在外部类型上实现外部 trait” 部分。
> 这一部分假设你已经阅读了之前的 [“newtype 模式用于在外部类型上实现外部 trait”][using-the-newtype-pattern] 部分。
### 为了类型安全和抽象而使用 newtype 模式
@ -14,7 +14,7 @@ newtype 模式可以用于一些其他我们还未讨论的功能,包括静态
另一个 newtype 模式的应用在于抽象掉一些类型的实现细节:例如,封装类型可以暴露出与直接使用其内部私有类型时所不同的公有 API以便限制其功能。
newtype 也可以隐藏其内部的泛型类型。例如,可以提供一个封装了 `HashMap<i32, String>``People` 类型,用来储存人名以及相应的 ID。使用 `People` 的代码只需与提供的公有 API 交互即可,比如向 `People` 集合增加名字字符串的方法,这样这些代码就无需知道在内部我们将一个 `i32` ID 赋予了这个名字了。newtype 模式是一种实现第十七章 “封装隐藏了实现细节” 部分所讨论的隐藏实现细节的封装的轻量级方法。
newtype 也可以隐藏其内部的泛型类型。例如,可以提供一个封装了 `HashMap<i32, String>``People` 类型,用来储存人名以及相应的 ID。使用 `People` 的代码只需与提供的公有 API 交互即可,比如向 `People` 集合增加名字字符串的方法,这样这些代码就无需知道在内部我们将一个 `i32` ID 赋予了这个名字了。newtype 模式是一种实现第十七章 [“封装隐藏了实现细节”][encapsulation-that-hides-implementation-details] 部分所讨论的隐藏实现细节的封装的轻量级方法。
### 类型别名用来创建类型同义词
@ -43,7 +43,7 @@ println!("x + y = {}", x + y);
Box<dyn Fn() + Send + 'static>
```
在函数签名或类型注解中每次都书写这个类型将是枯燥且易于出错的。想象一下如示例 19-32 这样全是如此代码的项目:
在函数签名或类型注解中每次都书写这个类型将是枯燥且易于出错的。想象一下如示例 19-24 这样全是如此代码的项目:
```rust
let f: Box<dyn Fn() + Send + 'static> = Box::new(|| println!("hi"));
@ -58,9 +58,9 @@ fn returns_long_type() -> Box<dyn Fn() + Send + 'static> {
}
```
<span class="caption">示例 19-32: 在很多地方使用名称很长的类型</span>
<span class="caption">示例 19-24: 在很多地方使用名称很长的类型</span>
类型别名通过减少项目中重复代码的数量来使其更加易于控制。这里我们为这个冗长的类型引入了一个叫做 `Thunk` 的别名,这样就可以如示例 19-33 所示将所有使用这个类型的地方替换为更短的 `Thunk`
类型别名通过减少项目中重复代码的数量来使其更加易于控制。这里我们为这个冗长的类型引入了一个叫做 `Thunk` 的别名,这样就可以如示例 19-25 所示将所有使用这个类型的地方替换为更短的 `Thunk`
```rust
type Thunk = Box<dyn Fn() + Send + 'static>;
@ -77,7 +77,7 @@ fn returns_long_type() -> Thunk {
}
```
<span class="caption">示例 19-33: 引入类型别名 `Thunk` 来减少重复</span>
<span class="caption">示例 19-25: 引入类型别名 `Thunk` 来减少重复</span>
这样就读写起来就容易多了!为类型别名选择一个好名字也可以帮助你表达意图(单词 *thunk* 表示会在之后被计算的代码,所以这是一个存放闭包的合适的名字)。
@ -98,8 +98,8 @@ pub trait Write {
这里出现了很多的 `Result<..., Error>`。为此,`std::io` 有这个类型别名声明:
```rust,ignore
type Result<T> = Result<T, std::io::Error>;
```rust
type Result<T> = std::result::Result<T, std::io::Error>;
```
因为这位于 `std::io` 中,可用的完全限定的别名是 `std::io::Result<T>` —— 也就是说,`Result<T, E>` 中 `E` 放入了 `std::io::Error`。`Write` trait 中的函数最终看起来像这样:
@ -128,7 +128,7 @@ fn bar() -> ! {
这读 “函数 `bar` 从不返回”,而从不返回的函数被称为 **发散函数***diverging functions*)。不能创建 `!` 类型的值,所以 `bar` 也不可能返回值。
不过一个不能创建值的类型有什么用呢?如果你回想一下示例 2-5 中的代码,曾经有一些看起来像这样的代码,如示例 19-34 所重现的:
不过一个不能创建值的类型有什么用呢?如果你回想一下示例 2-5 中的代码,曾经有一些看起来像这样的代码,如示例 19-26 所重现的:
```rust
# let guess = "3";
@ -141,9 +141,9 @@ let guess: u32 = match guess.trim().parse() {
# }
```
<span class="caption">示例 19-34: `match` 语句和一个以 `continue` 结束的分支</span>
<span class="caption">示例 19-26: `match` 语句和一个以 `continue` 结束的分支</span>
当时我们忽略了代码中的一些细节。在第六章 “`match` 控制流运算符” 部分,我们学习了 `match` 的分支必须返回相同的类型。如下代码不能工作:
当时我们忽略了代码中的一些细节。在第六章 [“`match` 控制流运算符”][the-match-control-flow-operator] 部分,我们学习了 `match` 的分支必须返回相同的类型。如下代码不能工作:
```rust,ignore,does_not_compile
let guess = match guess.trim().parse() {
@ -152,7 +152,7 @@ let guess = match guess.trim().parse() {
}
```
这里的 `guess` 必须既是整型 **也是** 字符串,而 Rust 要求 `guess` 只能是一个类型。那么 `continue` 返回了什么呢?为什么示例 19-34 中会允许一个分支返回 `u32` 而另一个分支却以 `continue` 结束呢?
这里的 `guess` 必须既是整型 **也是** 字符串,而 Rust 要求 `guess` 只能是一个类型。那么 `continue` 返回了什么呢?为什么示例 19-26 中会允许一个分支返回 `u32` 而另一个分支却以 `continue` 结束呢?
正如你可能猜到的,`continue` 的值是 `!`。也就是说,当 Rust 要计算 `guess` 的类型时,它查看这两个分支。前者是 `u32` 值,而后者是 `!` 值。因为 `!` 并没有一个值Rust 决定 `guess` 的类型是 `u32`
@ -198,11 +198,11 @@ let s2: str = "How's it going?";
Rust 需要知道应该为特定类型的值分配多少内存,同时所有同一类型的值必须使用相同数量的内存。如果允许编写这样的代码,也就意味着这两个 `str` 需要占用完全相同大小的空间,不过它们有着不同的长度。这也就是为什么不可能创建一个存放动态大小类型的变量的原因。
那么该怎么办呢?你已经知道了这种问题的答案:`s1` 和 `s2` 的类型是 `&str` 而不是 `str`。如果你回想第四章 “字符串 slice” 部分slice 数据结储存了开始位置和 slice 的长度。
那么该怎么办呢?你已经知道了这种问题的答案:`s1` 和 `s2` 的类型是 `&str` 而不是 `str`。如果你回想第四章 [“字符串 slice”][string-slices] 部分slice 数据结储存了开始位置和 slice 的长度。
所以虽然 `&T` 是一个储存了 `T` 所在的内存位置的单个值,`&str` 则是 **两个** 值:`str` 的地址和其长度。这样,`&str` 就有了一个在编译时可以知道的大小:它是 `usize` 长度的两倍。也就是说,我们总是知道 `&str` 的大小,而无论其引用的字符串是多长。这里是 Rust 中动态大小类型的常规用法:他们有一些额外的元信息来储存动态信息的大小。这引出了动态大小类型的黄金规则:必须将动态大小类型的值置于某种指针之后。
可以将 `str` 与所有类型的指针结合:比如 `Box<str>``Rc<str>`。事实上之前我们已经见过了不过是另一个动态大小类型trait。每一个 trait 都是一个可以通过 trait 名称来引用的动态大小类型。在第十七章 “为使用不同类型的值而设计的 trait 对象” 部分,我们提到了为了将 trait 用于 trait 对象,必须将他们放入指针之后,比如 `&Trait``Box<Trait>``Rc<Trait>` 也可以)。trait 之所以是动态大小类型的是因为只有这样才能使用它。
可以将 `str` 与所有类型的指针结合:比如 `Box<str>``Rc<str>`。事实上之前我们已经见过了不过是另一个动态大小类型trait。每一个 trait 都是一个可以通过 trait 名称来引用的动态大小类型。在第十七章 [“为使用不同类型的值而设计的 trait 对象”][using-trait-objects-that-allow-for-values-of-different-types] 部分,我们提到了为了将 trait 用于 trait 对象,必须将他们放入指针之后,比如 `&Trait``Box<Trait>``Rc<Trait>` 也可以)。
为了处理 DSTRust 有一个特定的 trait 来决定一个类型的大小是否在编译时可知:这就是 `Sized` trait。这个 trait 自动为编译器在编译时就知道大小的类型实现。另外Rust 隐式的为每一个泛型函数增加了 `Sized` bound。也就是说对于如下泛型函数定义
@ -233,3 +233,12 @@ fn generic<T: ?Sized>(t: &T) {
另外注意我们将 `t` 参数的类型从 `T` 变为了 `&T`:因为其类型可能不是 `Sized` 的,所以需要将其置于某种指针之后。在这个例子中选择了引用。
接下来,让我们讨论一下函数和闭包!
[encapsulation-that-hides-implementation-details]:
ch17-01-what-is-oo.html#encapsulation-that-hides-implementation-details
[string-slices]: ch04-03-slices.html#string-slices
[the-match-control-flow-operator]:
ch06-02-match.html#the-match-control-flow-operator
[using-trait-objects-that-allow-for-values-of-different-types]:
ch17-02-trait-objects.html#using-trait-objects-that-allow-for-values-of-different-types
[using-the-newtype-pattern]: ch19-03-advanced-traits.html#using-the-newtype-pattern-to-implement-external-traits-on-external-types

View File

@ -2,13 +2,13 @@
> [ch19-05-advanced-functions-and-closures.md](https://github.com/rust-lang/book/blob/master/src/ch19-05-advanced-functions-and-closures.md)
> <br>
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
> commit 426f3e4ec17e539ae9905ba559411169d303a031
最后我们将探索一些有关函数和闭包的高级功能:函数指针以及返回值闭包。
### 函数指针
我们讨论过了如何向函数传递闭包;也可以向函数传递常规函数!这在我们希望传递已经定义的函数而不是重新定义闭包作为参数是很有用。通过函数指针允许我们使用函数作为另一个函数的参数。函数的类型是 `fn` (使用小写的 “f” )以免与 `Fn` 闭包 trait 相混淆。`fn` 被称为 **函数指针***function pointer*)。指定参数为函数指针的语法类似于闭包,如示例 19-35 所示:
我们讨论过了如何向函数传递闭包;也可以向函数传递常规函数!这在我们希望传递已经定义的函数而不是重新定义闭包作为参数是很有用。通过函数指针允许我们使用函数作为另一个函数的参数。函数的类型是 `fn` (使用小写的 “f” )以免与 `Fn` 闭包 trait 相混淆。`fn` 被称为 **函数指针***function pointer*)。指定参数为函数指针的语法类似于闭包,如示例 19-27 所示:
<span class="filename">文件名: src/main.rs</span>
@ -28,7 +28,7 @@ fn main() {
}
```
<span class="caption">示例 19-34: 使用 `fn` 类型接受函数指针作为参数</span>
<span class="caption">示例 19-27: 使用 `fn` 类型接受函数指针作为参数</span>
这会打印出 `The answer is: 12`。`do_twice` 中的 `f` 被指定为一个接受一个 `i32` 参数并返回 `i32``fn`。接着就可以在 `do_twice` 函数体中调用 `f`。在 `main` 中,可以将函数名 `add_one` 作为第一个参数传递给 `do_twice`
@ -58,7 +58,7 @@ let list_of_strings: Vec<String> = list_of_numbers
.collect();
```
注意这里必须使用 “高级 trait” 部分讲到的完全限定语法,因为存在多个叫做 `to_string` 的函数;这里使用了定义于 `ToString` trait 的 `to_string` 函数,标准库为所有实现了 `Display` 的类型实现了这个 trait。
注意这里必须使用 [“高级 trait”][advanced-traits] 部分讲到的完全限定语法,因为存在多个叫做 `to_string` 的函数;这里使用了定义于 `ToString` trait 的 `to_string` 函数,标准库为所有实现了 `Display` 的类型实现了这个 trait。
另一个实用的模式暴露了元组结构体和元组结构体枚举成员的实现细节。这些项使用 `()` 作为初始化语法,这看起来就像函数调用,同时它们确实被实现为返回由参数构造的实例的函数。它们也被称为实现了闭包 trait 的函数指针,并可以采用类似如下的方式调用:
@ -74,7 +74,7 @@ let list_of_statuses: Vec<Status> =
.collect();
```
一些人倾向于函数风格,一些人喜欢闭包。这两种形式最终都会产生同样的代码,所以请使用对你来说更明白的形式吧。
这里创建了 `Status::Value` 实例,它通过 `map` 用范围的每一个 `u32` 值调用 `Status::Value` 的初始化函数。一些人倾向于函数风格,一些人喜欢闭包。这两种形式最终都会产生同样的代码,所以请使用对你来说更明白的形式吧。
### 返回闭包
@ -112,6 +112,11 @@ fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
}
```
这段代码正好可以编译。关于 trait 对象的更多内容,请回顾第十七章的 “为使用不同类型的值而设计的 trait 对象” 部分。
这段代码正好可以编译。关于 trait 对象的更多内容,请回顾第十七章的 [“为使用不同类型的值而设计的 trait 对象”][using-trait-objects-that-allow-for-values-of-different-types] 部分。
接下来让我们学习宏!
[advanced-traits]:
ch19-03-advanced-traits.html#advanced-traits
[using-trait-objects-that-allow-for-values-of-different-types]:
ch17-02-trait-objects.html#using-trait-objects-that-allow-for-values-of-different-types

View File

@ -2,15 +2,13 @@
> [ch19-06-macros.md](https://github.com/rust-lang/book/blob/master/src/ch19-06-macros.md)
> <br>
> commit 6babe749f8d97131b97c3cb9e7ca76e6115b90c1
> commit 7ddc46460f09a5cd9bd2a620565bdc20b3315ea9
我们已经在本书中使用过像 `println!` 这样的宏了,不过还没完全探索什么是宏以及它是如何工作的。**宏***Macro*)指的是 Rust 中一系列的功能:
我们已经在本书中使用过像 `println!` 这样的宏了,不过还没完全探索什么是宏以及它是如何工作的。**宏***Macro*)指的是 Rust 中一系列的功能:**声明***Declarative*)宏,使用 `macro_rules!`,和三种 **过程***Procedural*)宏:
* **声明***Declarative*)宏,使用 `macro_rules!`
* **过程***Procedural*),其有三种类型:
* 自定义 `#[derive]`
* 类属性Attribute
* 类函数宏
* 自定义 `#[derive]` 宏在结构体和枚举上指定通过 `derive` 属性添加的代码
* 类属性Attribute宏定义可用于任意项的自定义属性
* 类函数宏看起来像函数不过作用于作为参数传递的 token。
我们会依次讨论每一种宏,不过首要的是,为什么已经有了函数还需要宏呢?
@ -38,7 +36,7 @@ let v: Vec<u32> = vec![1, 2, 3];
也可以使用 `vec!` 宏来构造两个整数的 vector 或五个字符串 slice 的 vector 。但却无法使用函数做相同的事情,因为我们无法预先知道参数值的数量和类型。
在示例 19-36 中展示了一个 `vec!` 稍微简化的定义。
在示例 19-28 中展示了一个 `vec!` 稍微简化的定义。
<span class="filename">文件名: src/lib.rs</span>
@ -57,7 +55,7 @@ macro_rules! vec {
}
```
<span class="caption">示例 19-36: 一个 `vec!` 宏定义的简化版本</span>
<span class="caption">示例 19-28: 一个 `vec!` 宏定义的简化版本</span>
> 注意:标准库中实际定义的 `vec!` 包括预分配适当量的内存的代码。这部分为代码优化,为了让示例简化,此处并没有包含在内。
@ -68,7 +66,7 @@ macro_rules! vec {
`vec!` 宏的结构和 `match` 表达式的结构类似。此处有一个单边模式 `( $( $x:expr ),* )` ,后跟 `=>` 以及和模式相关的代码块。如果模式匹配,该相关代码块将被执行。假设这只是这个宏中的模式,且只有一个有效匹配,其他任何匹配都是错误的。更复杂的宏会有多个单边模式。
宏定义中有效模式语法和在第十八章提及的模式语法是不同的,因为宏模式所匹配的是 Rust 代码结构而不是值。回过头来检查下示例 D-1 中模式片段什么意思。对于全部的宏模式语法,请查阅[参考]。
宏定义中有效模式语法和在第十八章提及的模式语法是不同的,因为宏模式所匹配的是 Rust 代码结构而不是值。回过头来检查下示例 19-28 中模式片段什么意思。对于全部的宏模式语法,请查阅[参考]。
[参考]: https://doc.rust-lang.org/reference/macros.html
@ -98,9 +96,9 @@ temp_vec
第二种形式的宏被称为 **过程宏***procedural macros*),因为它们更像函数(一种过程类型)。过程宏接收 Rust 代码作为输入,在这些代码上进行操作,然后产生另一些代码作为输出,而非像声明式宏那样匹配对应模式然后以另一部分代码替换当前代码。
有三种类型的过程宏,不过它们的工作方式都类似。其一,其定义必须位于一种特殊类型的属于它们自己的 crate 中。这么做出于复杂的技术原因,将来我们希望能够消除这些限制。
有三种类型的过程宏(自定义 derive类属性和类函数,不过它们的工作方式都类似。
其二,使用这些宏需采用类似示例 19-37 所示的代码形式,其中 `some_attribute` 是一个使用特定宏的占位符。
当创建过程宏时,其定义必须位于一种特殊类型的属于它们自己的 crate 中。这么做出于复杂的技术原因,将来我们希望能够消除这些限制。使用这些宏需采用类似示例 19-29 所示的代码形式,其中 `some_attribute` 是一个使用特定宏的占位符。
<span class="filename">文件名: src/lib.rs</span>
@ -112,7 +110,7 @@ pub fn some_name(input: TokenStream) -> TokenStream {
}
```
<span class="caption">示例 19-37: 一个使用过程宏的例子</span>
<span class="caption">示例 19-29: 一个使用过程宏的例子</span>
过程宏包含一个函数,这也是其得名的原因:“过程” 是 “函数” 的同义词。那么为何不叫 “函数宏” 呢?好吧,有一个过程宏是 “类函数” 的,叫成函数会产生混乱。无论如何,定义过程宏的函数接受一个 `TokenStream` 作为输入并产生一个 `TokenStream` 作为输出。这也就是宏的核心:宏所处理的源代码组成了输入 `TokenStream`,同时宏生成的代码是输出 `TokenStream`。最后,函数上有一个属性;这个属性表明过程宏的类型。在同一 crate 中可以有多种的过程宏。
@ -120,7 +118,7 @@ pub fn some_name(input: TokenStream) -> TokenStream {
### 如何编写自定义 `derive`
让我们创建一个 `hello_macro` crate其包含名为 `HelloMacro` 的 trait 和关联函数 `hello_macro`。不同于让 crate 的用户为其每一个类型实现 `HelloMacro` trait我们将会提供一个过程式宏以便用户可以使用 `#[derive(HelloMacro)]` 注解他们的类型来得到 `hello_macro` 函数的默认实现。该默认实现会打印 `Hello, Macro! My name is TypeName!`,其中 `TypeName` 为定义了 trait 的类型名。换言之,我们会创建一个 crate使程序员能够写类似示例 19-38 中的代码。
让我们创建一个 `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>
@ -136,7 +134,7 @@ fn main() {
}
```
<span class="caption">示例 19-38: crate 用户所写的能够使用过程式宏的代码</span>
<span class="caption">示例 19-30: crate 用户所写的能够使用过程式宏的代码</span>
运行该代码将会打印 `Hello, Macro! My name is Pancakes!` 第一步是像下面这样新建一个库 crate
@ -197,7 +195,7 @@ syn = "0.14.4"
quote = "0.6.3"
```
为定义一个过程式宏,请将示例 19-39 中的代码放在 `hello_macro_derive` crate 的 *src/lib.rs* 文件里面。注意这段代码在我们添加 `impl_hello_macro` 函数的定义之前是无法编译的。
为定义一个过程式宏,请将示例 19-31 中的代码放在 `hello_macro_derive` crate 的 *src/lib.rs* 文件里面。注意这段代码在我们添加 `impl_hello_macro` 函数的定义之前是无法编译的。
<span class="filename">文件名: hello_macro_derive/src/lib.rs</span>
@ -231,18 +229,20 @@ pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
}
```
<span class="caption">示例 19-39: 大多数过程式宏处理 Rust 代码时所需的代码</span>
<span class="caption">示例 19-31: 大多数过程式宏处理 Rust 代码时所需的代码</span>
注意在 19-39 中分离函数的方式,这将和你几乎所见到或创建的每一个过程宏都一样,因为这让编写一个过程式宏更加方便。在 `impl_hello_macro` 被调用的地方所选择做的什么依赖于该过程式宏的目的而有所不同。
注意 `hello_macro_derive` 函数中代码分割的方式,它负责解析 `TokenStream`,而 `impl_hello_macro` 函数则负责转换语法树:这让编写一个过程式宏更加方便。外部函数中的代码(在这里是 `hello_macro_derive`)几乎在所有你能看到或创建的过程宏 crate 中都一样。内部函数(在这里是 `impl_hello_macro`)的函数体中所指定的代码则依过程宏的目的而各有不同。
现在,我们已经引入了 三个新的 crate`proc_macro` 、 [`syn`] 和 [`quote`] 。Rust 自带 `proc_macro` crate因此无需将其加到 *Cargo.toml* 文件的依赖中。`proc_macro` crate 是编译器用来读取和操作我们 Rust 代码的 API。`syn` crate 将字符串中的 Rust 代码解析成为一个可以操作的数据结构。`quote` 则将 `syn` 解析的数据结构反过来传入到 Rust 代码中。这些 crate 让解析任何我们所要处理的 Rust 代码变得更简单:为 Rust 编写整个的解析器并不是一件简单的工作。
现在,我们已经引入了三个新的 crate`proc_macro` 、 [`syn`] 和 [`quote`] 。Rust 自带 `proc_macro` crate因此无需将其加到 *Cargo.toml* 文件的依赖中。`proc_macro` crate 是编译器用来读取和操作我们 Rust 代码的 API。
[`syn`]: https://crates.io/crates/syn
[`quote`]: https://crates.io/crates/quote
`syn` crate 将字符串中的 Rust 代码解析成为一个可以操作的数据结构。`quote` 则将 `syn` 解析的数据结构反过来传入到 Rust 代码中。这些 crate 让解析任何我们所要处理的 Rust 代码变得更简单:为 Rust 编写整个的解析器并不是一件简单的工作。
当用户在一个类型上指定 `#[derive(HelloMacro)]` 时,`hello_macro_derive` 函数将会被调用。原因在于我们已经使用 `proc_macro_derive` 及其指定名称对 `hello_macro_derive` 函数进行了注解:`HelloMacro` ,其匹配到 trait 名,这是大多数过程宏遵循的习惯。
该函数首先将来自 `TokenStream``input` 转换为一个我们可以解释和操作的数据结构。这正是 `syn` 派上用场的地方。`syn` 中的 `parse_derive_input` 函数获取一个 `TokenStream` 并返回一个表示解析出 Rust 代码的 `DeriveInput` 结构体。示例 19-40 展示了从字符串 `struct Pancakes;` 中解析出来的 `DeriveInput` 结构体的相关部分:
该函数首先将来自 `TokenStream``input` 转换为一个我们可以解释和操作的数据结构。这正是 `syn` 派上用场的地方。`syn` 中的 `parse_derive_input` 函数获取一个 `TokenStream` 并返回一个表示解析出 Rust 代码的 `DeriveInput` 结构体。示例 19-32 展示了从字符串 `struct Pancakes;` 中解析出来的 `DeriveInput` 结构体的相关部分:
```rust,ignore
DeriveInput {
@ -264,7 +264,7 @@ DeriveInput {
}
```
<span class="caption">示例 19-40: 解析示例 19-38 中带有宏属性的代码时得到的 `DeriveInput` 实例</span>
<span class="caption">示例 19-32: 解析示例 19-30 中带有宏属性的代码时得到的 `DeriveInput` 实例</span>
该结构体的字段展示了我们解析的 Rust 代码是一个类单元结构体,其 `ident` identifier表示名字`Pancakes`。该结构体里面有更多字段描述了所有类型的 Rust 代码,查阅 [`syn` 中 `DeriveInput` 的文档][syn-docs] 以获取更多信息。
@ -274,7 +274,7 @@ DeriveInput {
你可能也注意到了,当调用 `parse_derive_input``parse` 失败时。在错误时 panic 对过程宏来说是必须的,因为 `proc_macro_derive` 函数必须返回 `TokenStream` 而不是 `Result`,以此来符合过程宏的 API。这里选择用 `unwrap` 来简化了这个例子;在生产代码中,则应该通过 `panic!``expect` 来提供关于发生何种错误的更加明确的错误信息。
现在我们有了将注解的 Rust 代码从 `TokenStream` 转换为 `DeriveInput` 实例的代码,让我们来创建在注解类型上实现 `HelloMacro` trait 的代码,如示例 19-41 所示。
现在我们有了将注解的 Rust 代码从 `TokenStream` 转换为 `DeriveInput` 实例的代码,让我们来创建在注解类型上实现 `HelloMacro` trait 的代码,如示例 19-33 所示。
<span class="filename">文件名: hello_macro_derive/src/lib.rs</span>
@ -292,9 +292,9 @@ fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream {
}
```
<span class="caption">示例 19-41: 使用解析过的 Rust 代码实现 `HelloMacro` trait</span>
<span class="caption">示例 19-33: 使用解析过的 Rust 代码实现 `HelloMacro` trait</span>
我们得到一个包含以 `ast.ident` 作为注解类型名字(标识符)的 `Ident` 结构体实例。示例 19-40 中的结构体表明当 `impl_hello_macro` 函数运行于示例 19-38 中的代码上时 `ident` 字段的值是 `"Pancakes"`。因此,示例 19-41`name` 变量会包含一个 `Ident` 结构体的实例,当打印时,会是字符串 `"Pancakes"`,也就是示例 19-38 中结构体的名称。
我们得到一个包含以 `ast.ident` 作为注解类型名字(标识符)的 `Ident` 结构体实例。示例 19-32 中的结构体表明当 `impl_hello_macro` 函数运行于示例 19-30 中的代码上时 `ident` 字段的值是 `"Pancakes"`。因此,示例 19-33`name` 变量会包含一个 `Ident` 结构体的实例,当打印时,会是字符串 `"Pancakes"`,也就是示例 19-30 中结构体的名称。
`quote!` 让我们可以编写希望返回的 Rust diamagnetic。`quote!` 宏执行的直接结果并不是编译器所期望的并需要转换为 `TokenStream`。为此需要调用 `into` 方法它会消费这个中间表示intermediate representationIR并返回所需的 `TokenStream` 类型值。
@ -306,7 +306,7 @@ fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream {
此处所使用的 `stringify!` 为 Rust 内置宏。其接收一个 Rust 表达式,如 `1 + 2` 然后在编译时将表达式转换为一个字符串常量,如 `"1 + 2"` 。这与 `format!``println!` 是不同的,它计算表达式并将结果转换为 `String` 。有一种可能的情况是,所输入的 `#name` 可能是一个需要打印的表达式,因此我们用 `stringify!``stringify!` 编译时也保留了一份将 `#name` 转换为字符串之后的内存分配。
此时,`cargo build` 应该都能成功编译 `hello_macro``hello_macro_derive` 。我们将这些 crate 连接到示例 19-38 的代码中来看看过程宏的行为!在 *projects* 目录下用 `cargo new pancakes` 命令新建一个二进制项目。需要将 `hello_macro``hello_macro_derive` 作为依赖加到 `pancakes` 包的 *Cargo.toml* 文件中去。如果你正将 `hello_macro``hello_macro_derive` 的版本发布到 *https://crates.io/* 上,其应为正规依赖;如果不是,则可以像下面这样将其指定为 `path` 依赖:
此时,`cargo build` 应该都能成功编译 `hello_macro``hello_macro_derive` 。我们将这些 crate 连接到示例 19-38 的代码中来看看过程宏的行为!在 *projects* 目录下用 `cargo new pancakes` 命令新建一个二进制项目。需要将 `hello_macro``hello_macro_derive` 作为依赖加到 `pancakes` 包的 *Cargo.toml* 文件中去。如果你正将 `hello_macro``hello_macro_derive` 的版本发布到 [crates.io](https://crates.io/) 上,其应为正规依赖;如果不是,则可以像下面这样将其指定为 `path` 依赖:
```toml
[dependencies]
@ -340,13 +340,15 @@ pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {
### 类函数宏
最后,类函数宏定义看起来像函数调用的宏。例如,`sql!` 宏可能像这样被调用:
类函数宏定义看起来像函数调用的宏。类似于 `macro_rules!`,它们比函数更灵活;例如,可以接受未知数量的参数。然而 `macro_rules!` 宏只能使用之前 [“使用 `macro_rules!` 的声明宏用于通用元编程”][decl] 介绍的类匹配的语法定义。类函数宏获取 `TokenStream` 参数,其定义使用 Rust 代码操纵 `TokenStream`,就像另两种过程宏一样。一个类函数宏例子是可以像这样被调用的 `sql!` 宏:
[decl]: #declarative-macros-with-macro_rules-for-general-metaprogramming
```rust,ignore
let sql = sql!(SELECT * FROM posts WHERE id=1);
```
这个宏会解析其中的 SQL 语句并检查其是否是句法正确的。该宏应该被定义为如此:
这个宏会解析其中的 SQL 语句并检查其是否是句法正确的,这是比 `macro_rules!` 可以做到的更为复杂的处理。`sql!` 宏应该被定义为如此:
```rust,ignore
#[proc_macro]

View File

@ -2,7 +2,7 @@
> [ch20-00-final-project-a-web-server.md](https://github.com/rust-lang/book/blob/master/src/ch20-00-final-project-a-web-server.md)
> <br>
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
> commit c084bdd9ee328e7e774df19882ccc139532e53d8
这是一次漫长的旅途,不过我们到达了本书的结束。在本章中,我们将一同构建另一个项目,来展示最后几章所学,同时复习更早的章节。
@ -20,6 +20,6 @@
4. 创建一个合适的 HTTP 响应
5. 通过线程池改善 server 的吞吐量
不过在开始之前,需要提到一点细节:这里使用的方法并不是使用 Rust 构建 web server 最好的方法。*https://crates.io* 上有很多可用于生产环境的 crate它们提供了比我们所要编写的更为完整的 web server 和线程池实现。
不过在开始之前,需要提到一点细节:这里使用的方法并不是使用 Rust 构建 web server 最好的方法。[crates.io](https://crates.io/) 上有很多可用于生产环境的 crate它们提供了比我们所要编写的更为完整的 web server 和线程池实现。
然而,本章的目的在于学习,而不是走捷径。因为 Rust 是一个系统编程语言,我们能够选择处理什么层次的抽象,并能够选择比其他语言可能或可用的层次更低的层次。因此我们将自己编写一个基础的 HTTP server 和线程池,以便学习将来可能用到的 crate 背后的通用理念和技术。

View File

@ -2,7 +2,7 @@
> [ch20-01-single-threaded.md](https://github.com/rust-lang/book/blob/master/src/ch20-01-single-threaded.md)
> <br>
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
> commit f617d58c1a88dd2912739a041fd4725d127bf9fb
首先让我们创建一个可运行的单线程 web server不过在开始之前我们将快速了解一下构建 web server 所涉及到的协议。这些协议的细节超出了本书的范畴,不过一个简单的概括会提供我们所需的信息。
@ -283,7 +283,7 @@ fn handle_connection(mut stream: TcpStream) {
}
```
<span class="caption">示例 20-6: 匹配请求并区别处理 `/` 请求与其他请求</span>
<span class="caption">示例 20-6: 匹配请求并区别处理 */* 请求与其他请求</span>
首先,将与 */* 请求相关的数据硬编码进变量 `get`。因为我们将原始字节读取进了缓冲区,所以在 `get` 的数据开头增加 `b""` 字节字符串语法将其转换为字节字符串。接着检查 `buffer` 是否以 `get` 中的字节开头。如果是,这就是一个格式良好的 */* 请求,也就是 `if` 块中期望处理的成功情况,并会返回 HTML 文件内容的代码。

View File

@ -2,7 +2,7 @@
> [ch20-02-multithreaded.md](https://github.com/rust-lang/book/blob/master/src/ch20-02-multithreaded.md)
> <br>
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
> commit 120e76a0cc77c9cde52643f847ed777f8f441817
目前 server 会依次处理每一个请求,意味着它在完成第一个连接的处理之前不会处理第二个连接。如果 server 正接收越来越多的请求,这类串行操作会使性能越来越差。如果一个请求花费很长时间来处理,随后而来的请求则不得不等待这个长请求结束,即便这些新请求可以很快就处理完。我们需要修复这种情况,不过首先让我们实际尝试一下这个问题。
@ -195,7 +195,7 @@ impl ThreadPool {
}
```
这里选择 `usize` 作为 `size` 参数的类型,因为我们知道为负的线程数没有意义。我们还知道将使用 4 作为线程集合的元素数量,这也就是使用 `usize` 类型的原因,如第三章 “整数类型” 部分所讲。
这里选择 `usize` 作为 `size` 参数的类型,因为我们知道为负的线程数没有意义。我们还知道将使用 4 作为线程集合的元素数量,这也就是使用 `usize` 类型的原因,如第三章 [“整数类型”][integer-types] 部分所讲。
再次编译检查这段代码:
@ -218,9 +218,9 @@ error[E0599]: no method named `execute` found for type `hello::ThreadPool` in th
| ^^^^^^^
```
现在有了一个警告和一个错误。暂时先忽略警告,发生错误是因为并没有 `ThreadPool` 上的 `execute` 方法。回忆 “为有限数量的线程创建一个类似的接口” 部分我们决定线程池应该有与 `thread::spawn` 类似的接口,同时我们将实现 `execute` 函数来获取传递的闭包并将其传递给池中的空闲线程执行。
现在有了一个警告和一个错误。暂时先忽略警告,发生错误是因为并没有 `ThreadPool` 上的 `execute` 方法。回忆 [“为有限数量的线程创建一个类似的接口”](#creating-a-similar-interface-for-a-finite-number-of-threads) 部分我们决定线程池应该有与 `thread::spawn` 类似的接口,同时我们将实现 `execute` 函数来获取传递的闭包并将其传递给池中的空闲线程执行。
我们会在 `ThreadPool` 上定义 `execute` 函数来获取一个闭包参数。回忆第十三章的 “使用带有泛型和 `Fn` trait 的闭包” 部分,闭包作为参数时可以使用三个不同的 trait`Fn`、`FnMut` 和 `FnOnce`。我们需要决定这里应该使用哪种闭包。最终需要实现的类似于标准库的 `thread::spawn`,所以我们可以观察 `thread::spawn` 的签名在其参数中使用了何种 bound。查看文档会发现
我们会在 `ThreadPool` 上定义 `execute` 函数来获取一个闭包参数。回忆第十三章的 [“使用带有泛型和 `Fn` trait 的闭包”][storing-closures-using-generic-parameters-and-the-fn-traits] 部分,闭包作为参数时可以使用三个不同的 trait`Fn`、`FnMut` 和 `FnOnce`。我们需要决定这里应该使用哪种闭包。最终需要实现的类似于标准库的 `thread::spawn`,所以我们可以观察 `thread::spawn` 的签名在其参数中使用了何种 bound。查看文档会发现
```rust,ignore
pub fn spawn<F, T>(f: F) -> JoinHandle<T>
@ -644,7 +644,7 @@ impl Worker {
#### 实现 `execute` 方法
最后让我们实现 `ThreadPool` 上的 `execute` 方法。同时也要修改 `Job` 结构体:它将不再是结构体,`Job` 将是一个有着 `execute` 接收到的闭包类型的 trait 对象的类型别名。第十九章 “类型别名用来创建类型同义词” 部分提到过,类型别名允许将长的类型变短。观察示例 20-19
最后让我们实现 `ThreadPool` 上的 `execute` 方法。同时也要修改 `Job` 结构体:它将不再是结构体,`Job` 将是一个有着 `execute` 接收到的闭包类型的 trait 对象的类型别名。第十九章 [“类型别名用来创建类型同义词”][creating-type-synonyms-with-type-aliases] 部分提到过,类型别名允许将长的类型变短。观察示例 20-19
<span class="filename">文件名: src/lib.rs</span>
@ -657,7 +657,7 @@ impl Worker {
# use std::sync::mpsc;
# struct Worker {}
type Job = Box<FnOnce() + Send + 'static>;
type Job = Box<dyn FnOnce() + Send + 'static>;
impl ThreadPool {
// --snip--
@ -694,7 +694,7 @@ impl Worker {
println!("Worker {} got a job; executing.", id);
(*job)();
job();
}
});
@ -714,73 +714,6 @@ impl Worker {
调用 `recv` 会阻塞当前线程,所以如果还没有任务,其会等待直到有可用的任务。`Mutex<T>` 确保一次只有一个 `Worker` 线程尝试请求任务。
理论上这段代码应该能够编译。不幸的是Rust 编译器仍不够完美,会给出如下错误:
```text
error[E0161]: cannot move a value of type std::ops::FnOnce() +
std::marker::Send: the size of std::ops::FnOnce() + std::marker::Send cannot be
statically determined
--> src/lib.rs:63:17
|
63 | (*job)();
| ^^^^^^
```
这个错误非常的神秘,因为这个问题本身就很神秘。为了调用储存在 `Box<T>` (这正是 `Job` 别名的类型)中的 `FnOnce` 闭包,该闭包需要能将自己移动 **出** `Box<T>`,因为当调用这个闭包时,它获取 `self` 的所有权。通常来说,将值移动出 `Box<T>` 是不被允许的,因为 Rust 不知道 `Box<T>` 中的值将会有多大;回忆第十五章能够正常使用 `Box<T>` 是因为我们将未知大小的值储存进 `Box<T>` 从而得到已知大小的值。
第十七章曾见过,示例 17-15 中有使用了 `self: Box<Self>` 语法的方法,它允许方法获取储存在 `Box<T>` 中的 `Self` 值的所有权。这正是我们希望做的,然而不幸的是 Rust 不允许我们这么做Rust 当闭包被调用时行为的那部分并没有使用 `self: Box<Self>` 实现。所以这里 Rust 也不知道它可以使用 `self: Box<Self>` 来获取闭包的所有权并将闭包移动出 `Box<T>`
Rust 仍在努力改进提升编译器的过程中,不过将来示例 20-20 中的代码应该能够正常工作。有很多像你一样的人正在修复这个以及其他问题!当你结束了本书的阅读,我们希望看到你也成为他们中的一员。
不过目前让我们通过一个小技巧来绕过这个问题。可以显式的告诉 Rust 在这里我们可以使用 `self: Box<Self>` 来获取 `Box<T>` 中值的所有权,而一旦获取了闭包的所有权就可以调用它了。这涉及到定义一个新 trait它带有一个在签名中使用 `self: Box<Self>` 的方法 `call_box`,为任何实现了 `FnOnce()` 的类型定义这个 trait修改类型别名来使用这个新 trait并修改 `Worker` 使用 `call_box` 方法。这些修改如示例 20-21 所示:
<span class="filename">文件名: src/lib.rs</span>
```rust,ignore
trait FnBox {
fn call_box(self: Box<Self>);
}
impl<F: FnOnce()> FnBox for F {
fn call_box(self: Box<F>) {
(*self)()
}
}
type Job = Box<FnBox + Send + 'static>;
// --snip--
impl Worker {
fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
let thread = thread::spawn(move || {
loop {
let job = receiver.lock().unwrap().recv().unwrap();
println!("Worker {} got a job; executing.", id);
job.call_box();
}
});
Worker {
id,
thread,
}
}
}
```
<span class="caption">示例 20-21: 新增一个 trait `FnBox` 来绕过当前 `Box<FnOnce()>` 的限制</span>
首先,新建了一个叫做 `FnBox` 的 trait。这个 trait 有一个方法 `call_box`,它类似于其他 `Fn*` trait 中的 `call` 方法,除了它获取 `self: Box<Self>` 以便获取 `self` 的所有权并将值从 `Box<T>` 中移动出来。
接下来,为任何实现了 `FnOnce()` trait 的类型 `F` 实现 `FnBox` trait。这实际上意味着任何 `FnOnce()` 闭包都可以使用 `call_box` 方法。`call_box` 的实现使用 `(*self)()` 将闭包移动出 `Box<T>` 并调用此闭包。
现在我们需要 `Job` 类型别名是任何实现了新 trait `FnBox``Box`。这允许我们在得到 `Job` 值时使用 `Worker` 中的 `call_box`。为任何 `FnOnce()` 闭包都实现了 `FnBox` trait 意味着无需对实际在通道中发出的值做任何修改。现在 Rust 就能够理解我们的行为是正确的了。
这是非常狡猾且复杂的手段。无需过分担心他们并不是非常有道理;总有一天,这一切将是毫无必要的。
通过这个技巧,线程池处于可以运行的状态了!执行 `cargo run` 并发起一些请求:
```text
@ -826,9 +759,9 @@ Worker 2 got a job; executing.
成功了!现在我们有了一个可以异步执行连接的线程池!它绝不会创建超过四个线程,所以当 server 收到大量请求时系统也不会负担过重。如果请求 */sleep*server 也能够通过另外一个线程处理其他请求。
注意如果同时在多个浏览器窗口打开 */sleep*,它们可能会彼此间隔地加载 5 秒,因为一些浏览器处于缓存的原因会顺序执行相同请求的多个实例。这些限制并不是由于我们的 web server 造成的。
> 注意如果同时在多个浏览器窗口打开 */sleep*,它们可能会彼此间隔地加载 5 秒,因为一些浏览器处于缓存的原因会顺序执行相同请求的多个实例。这些限制并不是由于我们的 web server 造成的。
在学习了第十八章的 `while let` 循环之后,你可能会好奇为何不能如此编写 worker 线程:
在学习了第十八章的 `while let` 循环之后,你可能会好奇为何不能如此编写 worker 线程,如示例 20-21 所示
<span class="filename">文件名: src/lib.rs</span>
@ -841,7 +774,7 @@ impl Worker {
while let Ok(job) = receiver.lock().unwrap().recv() {
println!("Worker {} got a job; executing.", id);
job.call_box();
job();
}
});
@ -853,8 +786,14 @@ impl Worker {
}
```
<span class="caption">示例 20-22: 一个使用 `while let``Worker::new` 替代实现</span>
<span class="caption">示例 20-21: 一个使用 `while let``Worker::new` 替代实现</span>
这段代码可以编译和运行,但是并不会产生所期望的线程行为:一个慢请求仍然会导致其他请求等待执行。其原因有些微妙:`Mutex` 结构体没有公有 `unlock` 方法,因为锁的所有权依赖 `lock` 方法返回的 `LockResult<MutexGuard<T>>``MutexGuard<T>` 的生命周期。这允许借用检查器在编译时确保绝不会在没有持有锁的情况下访问由 `Mutex` 守护的资源,不过如果没有认真的思考 `MutexGuard<T>` 的生命周期的话,也可能会导致比预期更久的持有锁。因为 `while` 表达式中的值在整个块一直处于作用域中,`job.call_box()` 调用的过程中其仍然持有锁,这意味着其他 worker 不能接收任务。
相反通过使用 `loop` 并在循环块之内而不是之外获取锁和任务,`lock` 方法返回的 `MutexGuard``let job` 语句结束之后立刻就被丢弃了。这确保了 `recv` 调用过程中持有锁,而在 `job.call_box()` 调用前锁就被释放了,这就允许并发处理多个请求了。
[creating-type-synonyms-with-type-aliases]:
ch19-04-advanced-types.html#creating-type-synonyms-with-type-aliases
[integer-types]: ch03-02-data-types.html#integer-types
[storing-closures-using-generic-parameters-and-the-fn-traits]:
ch13-01-closures.html#storing-closures-using-generic-parameters-and-the-fn-traits

View File

@ -2,9 +2,9 @@
> [ch20-03-graceful-shutdown-and-cleanup.md](https://github.com/rust-lang/book/blob/master/src/ch20-03-graceful-shutdown-and-cleanup.md)
> <br>
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
> commit 9a5a1bfaec3b7763e1bcfd31a2fb19fe95183746
示例 20-21 中的代码如期通过使用线程池异步的响应请求。这里有一些警告说 `workers`、`id` 和 `thread` 字段没有直接被使用,这提醒了我们并没有清理所有的内容。当使用不那么优雅的 <span class="keystroke">ctrl-C</span> 终止主线程时,所有其他线程也会立刻停止,即便它们正处于处理请求的过程中。
示例 20-21 中的代码如期通过使用线程池异步的响应请求。这里有一些警告说 `workers`、`id` 和 `thread` 字段没有直接被使用,这提醒了我们并没有清理所有的内容。当使用不那么优雅的 <span class="keystroke">ctrl-c</span> 终止主线程时,所有其他线程也会立刻停止,即便它们正处于处理请求的过程中。
现在我们要为 `ThreadPool` 实现 `Drop` trait 对线程池中的每一个线程调用 `join`,这样这些线程将会执行完他们的请求。接着会为 `ThreadPool` 实现一个告诉线程他们应该停止接收新请求并结束的方式。为了实践这些代码,修改 server 在优雅停机graceful shutdown之前只接受两个请求。
@ -117,7 +117,7 @@ impl Drop for ThreadPool {
### 向线程发送信号使其停止接收任务
有了这些修改,代码就能编译且没有任何警告。不过也有坏消息,这些代码还不能以我们期望的方式运行。问题的关键在于 `Worker` 中分配的线程所运行的闭包中的逻辑:调用 `join` 并不会关闭线程,因为他们一直 `loop` 来寻找任务。如果采用这个实现来尝试丢弃 `ThreadPool` ,则主线程会永远阻塞在等待第一个线程结束上。
有了所有这些修改,代码就能编译且没有任何警告。不过也有坏消息,这些代码还不能以我们期望的方式运行。问题的关键在于 `Worker` 中分配的线程所运行的闭包中的逻辑:调用 `join` 并不会关闭线程,因为他们一直 `loop` 来寻找任务。如果采用这个实现来尝试丢弃 `ThreadPool` ,则主线程会永远阻塞在等待第一个线程结束上。
为了修复这个问题,修改线程既监听是否有 `Job` 运行也要监听一个应该停止监听并退出无限循环的信号。所以通道将发送这个枚举的两个成员之一而不是 `Job` 实例:
@ -172,7 +172,7 @@ impl Worker {
Message::NewJob(job) => {
println!("Worker {} got a job; executing.", id);
job.call_box();
job();
},
Message::Terminate => {
println!("Worker {} was told to terminate.", id);
@ -195,7 +195,7 @@ impl Worker {
为了适用 `Message` 枚举需要将两个地方的 `Job` 修改为 `Message``ThreadPool` 的定义和 `Worker::new` 的签名。`ThreadPool` 的 `execute` 方法需要发送封装进 `Message::NewJob` 成员的任务。然后,在 `Worker::new` 中当从通道接收 `Message` 时,当获取到 `NewJob`成员会处理任务而收到 `Terminate` 成员则会退出循环。
通过这些修改,代码再次能够编译并继续按照期望的行为运行。不过还是会得到一个警告,因为并没有创建任何 `Terminate` 成员的消息。如示例 20-25 所示修改 `Drop` 实现来修复此问题:
通过这些修改,代码再次能够编译并继续按照示例 20-21 之后相同的行为运行。不过还是会得到一个警告,因为并没有创建任何 `Terminate` 成员的消息。如示例 20-25 所示修改 `Drop` 实现来修复此问题:
<span class="filename">文件名: src/lib.rs</span>
@ -289,13 +289,12 @@ Shutting down worker 3
<span class="filename">文件名: src/bin/main.rs</span>
```rust,ignore
extern crate hello;
use hello::ThreadPool;
use std::io::prelude::*;
use std::net::TcpListener;
use std::net::TcpStream;
use std::fs::File;
use std::fs;
use std::thread;
use std::time::Duration;
@ -330,15 +329,12 @@ fn handle_connection(mut stream: TcpStream) {
("HTTP/1.1 404 NOT FOUND\r\n\r\n", "404.html")
};
let mut file = File::open(filename).unwrap();
let mut contents = String::new();
let contents = fs::read_to_string(filename).unwrap();
file.read_to_string(&mut contents).unwrap();
let response = format!("{}{}", status_line, contents);
let response = format!("{}{}", status_line, contents);
stream.write(response.as_bytes()).unwrap();
stream.flush().unwrap();
stream.write(response.as_bytes()).unwrap();
stream.flush().unwrap();
}
```
@ -360,17 +356,7 @@ pub struct ThreadPool {
sender: mpsc::Sender<Message>,
}
trait FnBox {
fn call_box(self: Box<Self>);
}
impl<F: FnOnce()> FnBox for F {
fn call_box(self: Box<F>) {
(*self)()
}
}
type Job = Box<dyn FnBox + Send + 'static>;
type Job = Box<dyn FnOnce() + Send + 'static>;
impl ThreadPool {
/// 创建线程池。
@ -446,7 +432,7 @@ impl Worker {
Message::NewJob(job) => {
println!("Worker {} got a job; executing.", id);
job.call_box();
job();
},
Message::Terminate => {
println!("Worker {} was told to terminate.", id);
@ -471,7 +457,7 @@ impl Worker {
- 为库的功能增加测试
- 将 `unwrap` 调用改为更健壮的错误处理
- 使用 `ThreadPool` 进行其他不同于处理网络请求的任务
- 在 *https://crates.io/* 寻找一个线程池 crate 并使用它实现一个类似的 web server将其 API 和鲁棒性与我们的实现做对比
- 在 [crates.io](https://crates.io/) 上寻找一个线程池 crate 并使用它实现一个类似的 web server将其 API 和鲁棒性与我们的实现做对比
## 总结