check to ch08-02

This commit is contained in:
KaiserY 2018-01-18 15:39:53 +08:00
parent 7d18b54b5e
commit cc083887a2
9 changed files with 192 additions and 128 deletions

View File

@ -2,11 +2,11 @@
> [ch06-02-match.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch06-02-match.md) > [ch06-02-match.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch06-02-match.md)
> <br> > <br>
> commit 01dd4248621c2f510947592e47d16bdab9b14cf0 > commit 18fd30d70f4d6ee67e0a808710bf7a3135ef7ed6
Rust 有一个叫做 `match` 的极为强大的控制流运算符,它允许我们将一个值与一系列的模式相比较并根据匹配的模式执行相应代码。模式可由字面值、变量、通配符和许多其他内容构成;第十八章会涉及到所有不同种类的模式以及它们的作用。`match` 的力量来源于模式的表现力以及编译器检查,它确保了所有可能的情况都得到处理。 Rust 有一个叫做 `match` 的极为强大的控制流运算符,它允许我们将一个值与一系列的模式相比较并根据匹配的模式执行相应代码。模式可由字面值、变量、通配符和许多其他内容构成;第十八章会涉及到所有不同种类的模式以及它们的作用。`match` 的力量来源于模式的表现力以及编译器检查,它确保了所有可能的情况都得到处理。
可以把 `match` 表达式想象成某种硬币分类器:硬币滑入有着不同大小孔洞的轨道,每一个硬币都会掉入符合它大小的孔洞。同样地,值也会检查 `match` 的每一个模式,并且在遇到第一个 “符合” 的模式时,值会进入相关联的代码块并在执行中被使用。 可以把 `match` 表达式想象成某种硬币分类器:硬币滑入有着不同大小孔洞的轨道,每一个硬币都会掉入符合它大小的孔洞。同样地,值也会通过 `match` 的每一个模式,并且在遇到第一个 “符合” 的模式时,值会进入相关联的代码块并在执行中被使用。
因为刚刚提到了硬币,让我们用它们来作为一个使用 `match` 的例子!我们可以编写一个函数来获取一个未知的(美帝)硬币,并以一种类似验钞机的方式,确定它是何种硬币并返回它的美分值,如示例 6-3 中所示: 因为刚刚提到了硬币,让我们用它们来作为一个使用 `match` 的例子!我们可以编写一个函数来获取一个未知的(美帝)硬币,并以一种类似验钞机的方式,确定它是何种硬币并返回它的美分值,如示例 6-3 中所示:
@ -63,7 +63,7 @@ fn value_in_cents(coin: Coin) -> u32 {
### 绑定值的模式 ### 绑定值的模式
匹配分支的另一个有用的功能是可以绑定匹配的模式的部分值。这也就是如何从枚举成员中提取值。 匹配分支的另一个有用的功能是可以绑定匹配的模式的部分值。这也就是如何从枚举成员中提取值
作为一个例子让我们修改枚举的一个成员来存放数据。1999 年到 2008 年间,美帝在 25 美分的硬币的一侧为 50 个州的每一个都印刷了不同的设计。其他的硬币都没有这种区分州的设计,所以只有这些 25 美分硬币有特殊的价值。可以将这些信息加入我们的 `enum`,通过改变 `Quarter` 成员来包含一个 `State` 值,示例 6-4 中完成了这些修改: 作为一个例子让我们修改枚举的一个成员来存放数据。1999 年到 2008 年间,美帝在 25 美分的硬币的一侧为 50 个州的每一个都印刷了不同的设计。其他的硬币都没有这种区分州的设计,所以只有这些 25 美分硬币有特殊的价值。可以将这些信息加入我们的 `enum`,通过改变 `Quarter` 成员来包含一个 `State` 值,示例 6-4 中完成了这些修改:
@ -85,7 +85,7 @@ enum Coin {
<span class="caption">示例 6-4`Quarter` 成员也存放了一个 `UsState` 值的 `Coin` 枚举</span> <span class="caption">示例 6-4`Quarter` 成员也存放了一个 `UsState` 值的 `Coin` 枚举</span>
想象一下我们的一个朋友尝试收集所有 50 个州的 25 美分硬币。在根据硬币类型分类零钱的同时,也可以报告出每个 25 美分硬币所对应的州名称,这样如果我们的朋友没有的话,他可以把它加入收藏。 想象一下我们的一个朋友尝试收集所有 50 个州的 25 美分硬币。在根据硬币类型分类零钱的同时,也可以报告出每个 25 美分硬币所对应的州名称,这样如果我们的朋友没有的话,他可以将其加入收藏。
在这些代码的匹配表达式中,我们在匹配 `Coin::Quarter` 成员的分支的模式中增加了一个叫做 `state` 的变量。当匹配到 `Coin::Quarter` 时,变量 `state` 将会绑定 25 美分硬币所对应州的值。接着在那个分支的代码中使用 `state`,如下: 在这些代码的匹配表达式中,我们在匹配 `Coin::Quarter` 成员的分支的模式中增加了一个叫做 `state` 的变量。当匹配到 `Coin::Quarter` 时,变量 `state` 将会绑定 25 美分硬币所对应州的值。接着在那个分支的代码中使用 `state`,如下:
@ -155,7 +155,7 @@ None => None,
Some(i) => Some(i + 1), Some(i) => Some(i + 1),
``` ```
`Some(5)``Some(i)` 匹配吗?为什么不呢!它们是相同的成员。`i` 绑定了 `Some` 中包含的值,所以 `i` 的值是 `5`。接着匹配分支的代码被执行,所以我们将 `i` 的值加一并返回一个含有值 `6` 的新 `Some` `Some(5)``Some(i)` 匹配吗?当然匹配!它们是相同的成员。`i` 绑定了 `Some` 中包含的值,所以 `i` 的值是 `5`。接着匹配分支的代码被执行,所以我们将 `i` 的值加一并返回一个含有值 `6` 的新 `Some`
#### 匹配 `None` #### 匹配 `None`
@ -191,7 +191,6 @@ error[E0004]: non-exhaustive patterns: `None` not covered
| ^ pattern `None` not covered | ^ pattern `None` not covered
``` ```
Rust 知道我们没有覆盖所有可能的情况甚至知道那些模式被忘记了Rust 中的匹配是 **穷尽的***exhaustive*):必须穷举到最后的可能性来使代码有效。特别的在这个 `Option<T>` 的例子中Rust 防止我们忘记明确的处理 `None` 的情况,这使我们免于假设拥有一个实际上为空的值,这造成了之前提到过的价值亿万的错误。 Rust 知道我们没有覆盖所有可能的情况甚至知道那些模式被忘记了Rust 中的匹配是 **穷尽的***exhaustive*):必须穷举到最后的可能性来使代码有效。特别的在这个 `Option<T>` 的例子中Rust 防止我们忘记明确的处理 `None` 的情况,这使我们免于假设拥有一个实际上为空的值,这造成了之前提到过的价值亿万的错误。
### `_` 通配符 ### `_` 通配符

View File

@ -4,7 +4,7 @@
> <br> > <br>
> commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56 > commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56
`if let` 语法让我们以一种不那么冗长的方式结合 `if``let`,来处理匹配一个模式的值而忽略其他的值。考虑示例 6-6 中的程序,它匹配一个 `Option<u8>` 值并只希望当值为三时执行代码: `if let` 语法让我们以一种不那么冗长的方式结合 `if``let`,来处理只匹配一个模式的值而忽略其他模式的情况。考虑示例 6-6 中的程序,它匹配一个 `Option<u8>` 值并只希望当值为三时执行代码:
```rust ```rust
let some_u8_value = Some(0u8); let some_u8_value = Some(0u8);
@ -16,7 +16,7 @@ match some_u8_value {
<span class="caption">示例 6-6`match` 只关心当值为 `Some(3)` 时执行代码</span> <span class="caption">示例 6-6`match` 只关心当值为 `Some(3)` 时执行代码</span>
我们想要对 `Some(3)` 匹配进行操作不过不想处理任何其他 `Some<u8>` 值或 `None` 值。为了满足 `match` 表达式(穷尽性)的要求,必须在处理完这唯一的成员后加上 `_ => ()`,这样也要增加很多样板代码。 我们想要对 `Some(3)` 匹配进行操作但是不想处理任何其他 `Some<u8>` 值或 `None` 值。为了满足 `match` 表达式(穷尽性)的要求,必须在处理完这唯一的成员后加上 `_ => ()`,这样也要增加很多样板代码。
不过我们可以使用 `if let` 这种更短的方式编写。如下代码与示例 6-6 中的 `match` 行为一致: 不过我们可以使用 `if let` 这种更短的方式编写。如下代码与示例 6-6 中的 `match` 行为一致:
@ -33,7 +33,7 @@ if let Some(3) = some_u8_value {
换句话说,可以认为 `if let``match` 的一个语法糖,它当值匹配某一模式时执行代码而忽略所有其他值。 换句话说,可以认为 `if let``match` 的一个语法糖,它当值匹配某一模式时执行代码而忽略所有其他值。
可以在 `if let` 中包含一个 `else`。`else` 块中的代码与 `match` 表达式中的 `_` 分支块中的代码相同,这样的 `match` 表达式就等同于 `if let``else`。回忆一下示例 6-4 中 `Coin` 枚举的定义,它的 `Quarter` 成员包含一个 `UsState` 值。如果想要计数所有不是 25 美分的硬币的同时也报告 25 美分硬币所属的州,可以使用这样一个 `match` 表达式: 可以在 `if let` 中包含一个 `else`。`else` 块中的代码与 `match` 表达式中的 `_` 分支块中的代码相同,这样的 `match` 表达式就等同于 `if let``else`。回忆一下示例 6-4 中 `Coin` 枚举的定义,`Quarter` 成员也包含一个 `UsState` 值。如果想要计数所有不是 25 美分的硬币的同时也报告 25 美分硬币所属的州,可以使用这样一个 `match` 表达式:
```rust ```rust
# #[derive(Debug)] # #[derive(Debug)]

View File

@ -2,14 +2,14 @@
> [ch07-00-modules.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch07-00-modules.md) > [ch07-00-modules.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch07-00-modules.md)
> <br> > <br>
> commit b707dc664960f0ffc495c373900d6b13e434927d > commit a0b6dd108ac3896a771c1f6d74b2cd906b8bce19
在你刚开始编写 Rust 程序时,代码可能仅仅位于 `main` 函数里。随着代码数量的增长为了复用和更好地组织代码最终你会将功能移动到其他函数中。通过将代码分隔成更小的块每一个块代码自身就更易于理解。不过当你发现自己有太多的函数了该怎么办呢Rust 有一个模块系统可以有组织地复用代码。 在你刚开始编写 Rust 程序时,代码可能仅仅位于 `main` 函数中。随着代码量的增长为了复用和更好地组织代码最终你会将功能移动到其他函数中。通过将代码分隔成更小的块每一个块代码自身就更易于理解。不过当你发现自己有太多的函数了该怎么办呢Rust 有一个模块系统可以有组织地复用代码。
就跟你将代码行提取到一个函数中一样,也可以将函数(和其他类似结构体和枚举的代码)提取到不同模块中。**模块***module*)是一个包含函数或类型定义的命名空间,你可以选择这些定义能(公有)或不能(私有)在其模块外可见。下面是一个模块如何工作的梗概: 就跟你将代码行提取到一个函数中一样,也可以将函数(和其他类似结构体和枚举的代码)提取到不同模块中。**模块***module*)是一个包含函数或类型定义的命名空间,你可以选择这些定义能(公有)或不能(私有)在其模块外可见。下面是一个模块如何工作的梗概:
* 使用 `mod` 关键字声明新模块。此模块的代码要么直接位于声明之后的大括号中,要么位于另一个文件。 * 使用 `mod` 关键字声明新模块。此模块的代码要么直接位于声明之后的大括号中,要么位于另一个文件。
* 函数、类型、常量和模块默认都是私有的。可以使用 `pub` 关键字将其变成公有并在命名空间之外可见。 * 函数、类型、常量和模块默认都是私有的。可以使用 `pub` 关键字将其变成公有并在命名空间之外可见。
* `use` 关键字将模块或模块中的定义引入到作用域中以便于引用它们。 * `use` 关键字将模块或模块中的定义引入到作用域中以便于引用它们。
我们会逐一了解这每一部分并学习如何将它们结合在一起。 我们会逐一了解这每一部分并学习如何将它们结合在一起。

View File

@ -2,7 +2,7 @@
> [ch07-01-mod-and-the-filesystem.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch07-01-mod-and-the-filesystem.md) > [ch07-01-mod-and-the-filesystem.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch07-01-mod-and-the-filesystem.md)
> <br> > <br>
> commit c6a9e77a1b1ed367e0a6d5dcd222589ad392a8ac > commit 478fa6f92b6e7975f5e4da8a84a498fb873b937d
我们将通过使用 Cargo 创建一个新项目来开始我们的模块之旅,不过这次不再创建一个二进制 crate而是创建一个库 crate一个其他人可以作为依赖导入的项目。第二章猜猜看游戏中作为依赖使用的 `rand` 就是这样的 crate。 我们将通过使用 Cargo 创建一个新项目来开始我们的模块之旅,不过这次不再创建一个二进制 crate而是创建一个库 crate一个其他人可以作为依赖导入的项目。第二章猜猜看游戏中作为依赖使用的 `rand` 就是这样的 crate。
@ -35,7 +35,7 @@ Cargo 创建了一个空的测试来帮助我们开始库项目,不像使用 `
### 模块定义 ### 模块定义
对于 `communicator` 网络库,首先要定义一个叫做 `network` 的模块,它包含一个叫做 `connect` 的函数定义。Rust 中所有模块的定义以关键字 `mod` 开始。在 *src/lib.rs* 文件的开头在测试代码的上面增加这些代码: 对于 `communicator` 网络库,首先要定义一个叫做 `network` 的模块,它包含一个叫做 `connect` 的函数定义。Rust 中所有模块的定义以关键字 `mod` 开始。在 *src/lib.rs* 文件的开头在测试代码的上面增加这些代码:
<span class="filename">文件名: src/lib.rs</span> <span class="filename">文件名: src/lib.rs</span>
@ -140,9 +140,9 @@ communicator
└── server └── server
``` ```
如果这些模块有很多函数,而这些函数又很长,将难以在文件中寻找我们需要的代码。因为这些函数被嵌套进一个或多个块中,同时函数中的代码也会开始变长。这就有充分的理由将`client`、`network` 和 `server`每一个模块从 *src/lib.rs* 抽出并放入它们自己的文件中。 如果这些模块有很多函数,而这些函数又很长,将难以在文件中寻找我们需要的代码。因为这些函数被嵌套进一个或多个 `mod` 块中,同时函数中的代码也会开始变长。这就有充分的理由将 `client`、`network` 和 `server` 每一个模块从 *src/lib.rs* 抽出并放入它们自己的文件中。
首先,将 `client` 模块的代码替换为只有 `client` 模块声明,这样 *src/lib.rs* 看起来应该像这样 首先,将 `client` 模块的代码替换为只有 `client` 模块声明,这样 *src/lib.rs* 看起来应该像如示例 7-4 所示
<span class="filename">文件名: src/lib.rs</span> <span class="filename">文件名: src/lib.rs</span>
@ -160,6 +160,8 @@ mod network {
} }
``` ```
<span class="caption">示例 7-4提取出 `client` 模块的内容但仍将其声明留在 *src/lib.rs*</span>
这里我们仍然 **声明**`client` 模块,不过将代码块替换为了分号,这告诉了 Rust 在 `client` 模块的作用域中寻找另一个定义代码的位置。换句话说,`mod client;` 行意味着: 这里我们仍然 **声明**`client` 模块,不过将代码块替换为了分号,这告诉了 Rust 在 `client` 模块的作用域中寻找另一个定义代码的位置。换句话说,`mod client;` 行意味着:
```rust,ignore ```rust,ignore
@ -170,7 +172,7 @@ mod client {
那么现在需要创建对应模块名的外部文件。在 *src/* 目录创建一个 *client.rs* 文件,接着打开它并输入如下内容,它是上一步被去掉的 `client` 模块中的 `connect` 函数: 那么现在需要创建对应模块名的外部文件。在 *src/* 目录创建一个 *client.rs* 文件,接着打开它并输入如下内容,它是上一步被去掉的 `client` 模块中的 `connect` 函数:
<span class="filename">Filename: src/client.rs</span> <span class="filename">文件名: src/client.rs</span>
```rust ```rust
fn connect() { fn connect() {
@ -186,31 +188,35 @@ Rust 默认只知道 *src/lib.rs* 中的内容。如果想要对项目加入更
```text ```text
$ cargo build $ cargo build
Compiling communicator v0.1.0 (file:///projects/communicator) Compiling communicator v0.1.0 (file:///projects/communicator)
warning: function is never used: `connect`
warning: function is never used: `connect`, #[warn(dead_code)] on by default
--> src/client.rs:1:1 --> src/client.rs:1:1
| |
1 | fn connect() { 1 | / fn connect() {
| ^ 2 | | }
| |_^
|
= note: #[warn(dead_code)] on by default
warning: function is never used: `connect`, #[warn(dead_code)] on by default warning: function is never used: `connect`
--> src/lib.rs:4:5 --> src/lib.rs:4:5
| |
4 | fn connect() { 4 | / fn connect() {
| ^ 5 | | }
| |_____^
warning: function is never used: `connect`, #[warn(dead_code)] on by default warning: function is never used: `connect`
--> src/lib.rs:8:9 --> src/lib.rs:8:9
| |
8 | fn connect() { 8 | / fn connect() {
| ^ 9 | | }
| |_________^
``` ```
这些警告提醒我们有从未被使用的函数。目前不用担心这些警告,在本章后面的 “使用 `pub` 控制可见性” 部分会解决它们。好消息是,它们仅仅是警告,我们的项目能够成功编译。 这些警告提醒我们有从未被使用的函数。目前不用担心这些警告,在本章后面的 “使用 `pub` 控制可见性” 部分会解决它们。好消息是,它们仅仅是警告,我们的项目能够成功编译。
下面使用相同的模式将 `network` 模块提取到自己的文件中。删除 *src/lib.rs*`network` 模块的内容并在声明后加上一个分号,像这样: 下面使用相同的模式将 `network` 模块提取到自己的文件中。删除 *src/lib.rs*`network` 模块的内容并在声明后加上一个分号,像这样:
<span class="filename">Filename: src/lib.rs</span> <span class="filename">文件名: src/lib.rs</span>
```rust,ignore ```rust,ignore
mod client; mod client;
@ -220,7 +226,7 @@ mod network;
接着新建 *src/network.rs* 文件并输入如下内容: 接着新建 *src/network.rs* 文件并输入如下内容:
<span class="filename">Filename: src/network.rs</span> <span class="filename">文件名: src/network.rs</span>
```rust ```rust
fn connect() { fn connect() {
@ -236,7 +242,7 @@ mod server {
现在再次运行 `cargo build`。成功!不过我们还需要再提取出另一个模块:`server`。因为这是一个子模块——也就是模块中的模块——目前的将模块提取到对应名字的文件中的策略就不管用了。如果我们仍这么尝试则会出现错误。对 *src/network.rs* 的第一个修改是用 `mod server;` 替换 `server` 模块的内容: 现在再次运行 `cargo build`。成功!不过我们还需要再提取出另一个模块:`server`。因为这是一个子模块——也就是模块中的模块——目前的将模块提取到对应名字的文件中的策略就不管用了。如果我们仍这么尝试则会出现错误。对 *src/network.rs* 的第一个修改是用 `mod server;` 替换 `server` 模块的内容:
<span class="filename">Filename: src/network.rs</span> <span class="filename">文件名: src/network.rs</span>
```rust,ignore ```rust,ignore
fn connect() { fn connect() {
@ -247,14 +253,14 @@ mod server;
接着创建 *src/server.rs* 文件并输入需要提取的 `server` 模块的内容: 接着创建 *src/server.rs* 文件并输入需要提取的 `server` 模块的内容:
<span class="filename">Filename: src/server.rs</span> <span class="filename">文件名: src/server.rs</span>
```rust ```rust
fn connect() { fn connect() {
} }
``` ```
当尝试运行 `cargo build` 时,会出现如示例 7-4 中所示的错误: 当尝试运行 `cargo build` 时,会出现如示例 7-5 中所示的错误:
```text ```text
$ cargo build $ cargo build
@ -265,7 +271,7 @@ error: cannot declare a new module at this location
4 | mod server; 4 | mod server;
| ^^^^^^ | ^^^^^^
| |
note: maybe move this module `network` to its own directory via `network/mod.rs` note: maybe move this module `src/network.rs` to its own directory via `src/network/mod.rs`
--> src/network.rs:4:5 --> src/network.rs:4:5
| |
4 | mod server; 4 | mod server;
@ -277,11 +283,11 @@ note: ... or maybe `use` the module `server` instead of possibly redeclaring it
| ^^^^^^ | ^^^^^^
``` ```
<span class="caption">示例 7-4:尝试将 `server` 子模块提取到 *src/server.rs* 时出现的错误</span> <span class="caption">示例 7-5:尝试将 `server` 子模块提取到 *src/server.rs* 时出现的错误</span>
这个错误说明 “不能在这个位置新声明一个模块” 并指出 *src/network.rs* 中的 `mod server;` 这一行。看来 *src/network.rs**src/lib.rs* 在某些方面是不同的;继续阅读以理解这是为什么。 这个错误说明 “不能在这个位置新声明一个模块” 并指出 *src/network.rs* 中的 `mod server;` 这一行。看来 *src/network.rs**src/lib.rs* 在某些方面是不同的;继续阅读以理解这是为什么。
示例 7-4 中间的 note 事实上是非常有帮助的,因为它指出了一些我们还未讲到的操作: 示例 7-5 中间的 note 事实上是非常有帮助的,因为它指出了一些我们还未讲到的操作:
```text ```text
note: maybe move this module `network` to its own directory via note: maybe move this module `network` to its own directory via
@ -291,7 +297,7 @@ note: maybe move this module `network` to its own directory via
我们可以按照记录所建议的去操作,而不是继续使用之前的与模块同名文件的模式: 我们可以按照记录所建议的去操作,而不是继续使用之前的与模块同名文件的模式:
1. 新建一个叫做 *network***目录**,这是父模块的名字 1. 新建一个叫做 *network***目录**,这是父模块的名字
2. 将 *src/network.rs* 移动到新建的 *network* 目录中并重命名,现在它是 *src/network/mod.rs* 2. 将 *src/network.rs* 移动到新建的 *network* 目录中并重命名 *src/network/mod.rs*
3. 将子模块文件 *src/server.rs* 移动到 *network* 目录中 3. 将子模块文件 *src/server.rs* 移动到 *network* 目录中
如下是执行这些步骤的命令: 如下是执行这些步骤的命令:
@ -322,7 +328,7 @@ communicator
│ └── server.rs │ └── server.rs
``` ```
那么,当我们想要提取 `network::server` 模块时,为什么也必须将 *src/network.rs* 文件改名成 *src/network/mod.rs* 文件呢,还有为什么要将`network::server`的代码放入 *network* 目录的 *src/network/server.rs* 文件中,而不能将 `network::server` 模块提取到 *src/server.rs* 中呢?原因是如果 *server.rs* 文件在 *src* 目录中那么 Rust 就不能知道 `server` 应当是 `network` 的子模块。为了阐明这里 Rust 的行为,让我们考虑一下有着如下层级的另一个例子,它的所有定义都位于 *src/lib.rs* 中: 那么,当我们想要提取 `network::server` 模块时,为什么也必须将 *src/network.rs* 文件改名成 *src/network/mod.rs* 文件呢,还有为什么要将 `network::server` 的代码放入 *network* 目录的 *src/network/server.rs* 文件中,而不能将 `network::server` 模块提取到 *src/server.rs* 中呢?原因是如果 *server.rs* 文件在 *src* 目录中那么 Rust 就不能知道 `server` 应当是 `network` 的子模块。为了阐明这里 Rust 的行为,让我们考虑一下有着如下层级的另一个例子,所有定义都位于 *src/lib.rs* 中:
```text ```text
communicator communicator
@ -333,11 +339,11 @@ communicator
在这个例子中,仍然有这三个模块,`client`、`network` 和 `network::client`。如果按照与上面最开始将模块提取到文件中相同的步骤来操作,对于 `client` 模块会创建 *src/client.rs*。对于 `network` 模块,会创建 *src/network.rs*。但是接下来不能将 `network::client` 模块提取到 *src/client.rs* 文件中,因为它已经存在了,对应顶层的 `client` 模块!如果将 `client``network::client` 的代码都放入 *src/client.rs* 文件Rust 将无从可知这些代码是属于 `client` 还是 `network::client` 的。 在这个例子中,仍然有这三个模块,`client`、`network` 和 `network::client`。如果按照与上面最开始将模块提取到文件中相同的步骤来操作,对于 `client` 模块会创建 *src/client.rs*。对于 `network` 模块,会创建 *src/network.rs*。但是接下来不能将 `network::client` 模块提取到 *src/client.rs* 文件中,因为它已经存在了,对应顶层的 `client` 模块!如果将 `client``network::client` 的代码都放入 *src/client.rs* 文件Rust 将无从可知这些代码是属于 `client` 还是 `network::client` 的。
因此,一旦想要`network` 模块的子模块 `network::client` 提取到一个文件中,需要为 `network` 模块新建一个目录替代 *src/network.rs* 文件。接着 `network` 模块的代码将进入 *src/network/mod.rs* 文件,而子模块 `network::client` 将拥有其自己的文件 *src/network/client.rs*。现在顶层的 *src/client.rs* 中的代码毫无疑问的都属于 `client` 模块。 因此,为了`network` 模块的子模块 `network::client` 提取到一个文件中,需要为 `network` 模块新建一个目录替代 *src/network.rs* 文件。接着 `network` 模块的代码将进入 *src/network/mod.rs* 文件,而子模块 `network::client` 将拥有其自己的文件 *src/network/client.rs*。现在顶层的 *src/client.rs* 中的代码毫无疑问的都属于 `client` 模块。
### 模块文件系统的规则 ### 模块文件系统的规则
与文件系统相关的模块规则总结如下 让我们总结一下与文件有关的模块规则
* 如果一个叫做 `foo` 的模块没有子模块,应该将 `foo` 的声明放入叫做 *foo.rs* 的文件中。 * 如果一个叫做 `foo` 的模块没有子模块,应该将 `foo` 的声明放入叫做 *foo.rs* 的文件中。
* 如果一个叫做 `foo` 的模块有子模块,应该将 `foo` 的声明放入叫做 *foo/mod.rs* 的文件中。 * 如果一个叫做 `foo` 的模块有子模块,应该将 `foo` 的声明放入叫做 *foo/mod.rs* 的文件中。

View File

@ -2,33 +2,38 @@
> [ch07-02-controlling-visibility-with-pub.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch07-02-controlling-visibility-with-pub.md) > [ch07-02-controlling-visibility-with-pub.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch07-02-controlling-visibility-with-pub.md)
> <br> > <br>
> commit 0a4ed5875aeba78a81ae03ac73aeb84d2e2aca86 > commit 478fa6f92b6e7975f5e4da8a84a498fb873b937d
我们通过将 `network``network::server` 的代码分别移动到 *src/network/mod.rs**src/network/server.rs* 文件中解决了示例 7-4 中出现的错误信息。现在,`cargo build` 能够构建我们的项目,不过仍然有一些警告信息,表示 `client::connect`、`network::connect` 和`network::server::connect` 函数没有被使用: 我们通过将 `network``network::server` 的代码分别移动到 *src/network/mod.rs**src/network/server.rs* 文件中解决了示例 7-5 中出现的错误信息。现在,`cargo build` 能够构建我们的项目,不过仍然有一些警告信息,表示 `client::connect`、`network::connect` 和`network::server::connect` 函数没有被使用:
```text ```text
warning: function is never used: `connect`, #[warn(dead_code)] on by default warning: function is never used: `connect`
src/client.rs:1:1 --> src/client.rs:1:1
| |
1 | fn connect() { 1 | / fn connect() {
| ^ 2 | | }
| |_^
|
= note: #[warn(dead_code)] on by default
warning: function is never used: `connect`, #[warn(dead_code)] on by default warning: function is never used: `connect`
--> src/network/mod.rs:1:1 --> src/network/mod.rs:1:1
| |
1 | fn connect() { 1 | / fn connect() {
| ^ 2 | | }
| |_^
warning: function is never used: `connect`, #[warn(dead_code)] on by default warning: function is never used: `connect`
--> src/network/server.rs:1:1 --> src/network/server.rs:1:1
| |
1 | fn connect() { 1 | / fn connect() {
| ^ 2 | | }
| |_^
``` ```
那么为什么会出现这些错误信息呢?我们构建的是一个库,它的函数的目的是被 **用户** 使用,而不一定要被项目自身使用,所以不应该担心这些 `connect` 函数是未使用的。创建它们的意义就在于被另一个项目而不是被我们自己使用。 那么为什么会出现这些错误信息呢?毕竟我们构建的是一个库,它的函数的目的是被 **用户** 使用,而不一定要被项目自身使用,所以不应该担心这些 `connect` 函数是未使用的。创建它们的意义就在于被另一个项目而不是被我们自己使用。
为了理解为什么这个程序出现了这些警告,尝试作为另一个项目来使用这个 `connect` 库,从外部调用它们。为此,通过创建一个包含这些代码的 *src/main.rs* 文件,在与库 crate 相同的目录创建一个二进制 crate 为了理解为什么这个程序出现了这些警告,尝试在另一个项目中使用这个 `connect` 库,从外部调用它们。为此,通过创建一个包含这些代码的 *src/main.rs* 文件,在与库 crate 相同的目录创建一个二进制 crate
<span class="filename">文件名: src/main.rs</span> <span class="filename">文件名: src/main.rs</span>
@ -40,7 +45,7 @@ fn main() {
} }
``` ```
使用 `extern crate` 指令将 `communicator` 库 crate 引入到作用域,因为事实上我们的包现在包含 **两个** crate。Cargo 认为 *src/main.rs* 是一个二进制 crate 的根文件,与现存的以 *src/lib.rs* 为根文件的库 crate 相区分。这个模式在可执行项目中非常常见:大部分功能位于库 crate 中,而二进制 crate 使用这个库 crate。通过这种方式其他程序也可以使用这个库 crate这是一个很好的关注分离separation of concerns 使用 `extern crate` 指令将 `communicator` 库 crate 引入到作用域我们的包现在包含 **两个** crate。Cargo 认为 *src/main.rs* 是一个二进制 crate 的根文件,与现存的以 *src/lib.rs* 为根文件的库 crate 相区分。这个模式在可执行项目中非常常见:大部分功能位于库 crate 中,而二进制 crate 使用这个库 crate。通过这种方式其他程序也可以使用这个库 crate这是一个很好的关注分离separation of concerns
从一个外部 crate 的视角观察 `communicator` 库的内部,我们创建的所有模块都位于一个与 crate 同名的模块内部,`communicator`。这个顶层的模块被称为 crate 的 **根模块***root module*)。 从一个外部 crate 的视角观察 `communicator` 库的内部,我们创建的所有模块都位于一个与 crate 同名的模块内部,`communicator`。这个顶层的模块被称为 crate 的 **根模块***root module*)。
@ -49,7 +54,7 @@ fn main() {
我们的二进制 crate 如今正好调用了库中 `client` 模块的 `connect` 函数。然而,执行 `cargo build` 会在之前的警告之后出现一个错误: 我们的二进制 crate 如今正好调用了库中 `client` 模块的 `connect` 函数。然而,执行 `cargo build` 会在之前的警告之后出现一个错误:
```text ```text
error: module `client` is private error[E0603]: module `client` is private
--> src/main.rs:4:5 --> src/main.rs:4:5
| |
4 | communicator::client::connect(); 4 | communicator::client::connect();
@ -58,11 +63,11 @@ error: module `client` is private
啊哈!这告诉了我们 `client` 模块是私有的,这也正是那些警告的症结所在。这也是我们第一次在 Rust 上下文中涉及到 **公有***public*)和 **私有***private*的概念。Rust 所有代码的默认状态是私有的除了自己之外别人不允许使用这些代码。如果不在自己的项目中使用一个私有函数因为程序自身是唯一允许使用这个函数的代码Rust 会警告说函数未被使用。 啊哈!这告诉了我们 `client` 模块是私有的,这也正是那些警告的症结所在。这也是我们第一次在 Rust 上下文中涉及到 **公有***public*)和 **私有***private*的概念。Rust 所有代码的默认状态是私有的除了自己之外别人不允许使用这些代码。如果不在自己的项目中使用一个私有函数因为程序自身是唯一允许使用这个函数的代码Rust 会警告说函数未被使用。
一旦我们指定一个像 `client::connect` 的函数为公有,不光二进制 crate 中的函数调用是允许的,函数未被使用的警告也会消失。将其标记为公有让 Rust 知道了我们意在使函数在程序的外部被使用。现在这个可能的理论上的外部可用性使得 Rust 认为这个函数 “已经被使用”。因此。当某项被标记为公有Rust 不再要求它在程序自身被使用并停止警告某项未被使用。 一旦我们指定一个像 `client::connect` 的函数为公有,不光二进制 crate 中的函数调用是允许的,函数未被使用的警告也会消失。将其标记为公有让 Rust 知道了函数将会在程序的外部被使用。现在这个可能的理论上的外部可用性使得 Rust 认为这个函数 “已经被使用”。因此。当某项被标记为公有Rust 不再要求它在程序自身被使用并停止警告函数未被使用。
### 标记函数为公有 ### 标记函数为公有
为了告诉 Rust 某项为公有,在想要标记为公有的项的声明开头加上 `pub` 关键字。现在我们将致力于修复 `client::connect` 未被使用的警告,以及二进制 crate 中 “模块`client`是私有的” 的错误。像这样修改 *src/lib.rs* 使 `client` 模块公有: 为了告诉 Rust 将函数标记为公有,在声明的开头增加 `pub` 关键字。现在我们将致力于修复 `client::connect` 未被使用的警告,以及二进制 crate 中 “模块 `client` 是私有的” 的错误。像这样修改 *src/lib.rs* 使 `client` 模块公有:
<span class="filename">文件名: src/lib.rs</span> <span class="filename">文件名: src/lib.rs</span>
@ -75,7 +80,7 @@ mod network;
`pub` 写在 `mod` 之前。再次尝试构建: `pub` 写在 `mod` 之前。再次尝试构建:
```text ```text
error: function `connect` is private error[E0603]: function `connect` is private
--> src/main.rs:4:5 --> src/main.rs:4:5
| |
4 | communicator::client::connect(); 4 | communicator::client::connect();
@ -94,22 +99,26 @@ pub fn connect() {
再一次运行 `cargo build` 再一次运行 `cargo build`
```text ```text
warning: function is never used: `connect`, #[warn(dead_code)] on by default warning: function is never used: `connect`
--> src/network/mod.rs:1:1 --> src/network/mod.rs:1:1
| |
1 | fn connect() { 1 | / fn connect() {
| ^ 2 | | }
| |_^
|
= note: #[warn(dead_code)] on by default
warning: function is never used: `connect`, #[warn(dead_code)] on by default warning: function is never used: `connect`
--> src/network/server.rs:1:1 --> src/network/server.rs:1:1
| |
1 | fn connect() { 1 | / fn connect() {
| ^ 2 | | }
| |_^
``` ```
编译通过了,关于 `client::connect` 未被使用的警告消失了! 编译通过了,关于 `client::connect` 未被使用的警告消失了!
未被使用的代码并不总是意味着它们需要被设为公有的:如果你 **不** 希望这些函数成为公有 API 的一部分,未被使用的代码警告可能是在警告你这些代码不再需要并可以安全的删除它们。这也可能是警告你出 bug 了,如果你刚刚不小心删除了库中所有这个函数的调用。 未被使用的代码并不总是意味着它们需要被设为公有的:如果你 **不** 希望这些函数成为公有 API 的一部分,未被使用的代码警告可能是在提醒你这些代码不再需要并可以安全的删除它们。这也可能是警告你出 bug 了,如果你刚刚不小心删除了库中所有这个函数的调用。
当然我们的情况是,**确实** 希望另外两个函数也作为 crate 公有 API 的一部分,所以让我们也将其标记为 `pub` 并去掉剩余的警告。修改 *src/network/mod.rs* 为: 当然我们的情况是,**确实** 希望另外两个函数也作为 crate 公有 API 的一部分,所以让我们也将其标记为 `pub` 并去掉剩余的警告。修改 *src/network/mod.rs* 为:
@ -125,20 +134,24 @@ mod server;
并编译代码: 并编译代码:
```text ```text
warning: function is never used: `connect`, #[warn(dead_code)] on by default warning: function is never used: `connect`
--> src/network/mod.rs:1:1 --> src/network/mod.rs:1:1
| |
1 | pub fn connect() { 1 | / pub fn connect() {
| ^ 2 | | }
| |_^
|
= note: #[warn(dead_code)] on by default
warning: function is never used: `connect`, #[warn(dead_code)] on by default warning: function is never used: `connect`
--> src/network/server.rs:1:1 --> src/network/server.rs:1:1
| |
1 | fn connect() { 1 | / fn connect() {
| ^ 2 | | }
| |_^
``` ```
虽然将 `network::connect` 设为 `pub` 了我们仍然得到了一个未被使用函数的警告。这是因为模块中的函数是公有的,不过函数所在的 `network` 模块却不是公有的。这回我们是自内向外修改库文件的,而 `client::connect` 的时候是自外向内修改的。我们需要修改 *src/lib.rs*`network` 也是公有的: 虽然将 `network::connect` 设为 `pub` 了我们仍然得到了一个未被使用函数的警告。这是因为模块中的函数是公有的,不过函数所在的 `network` 模块却不是公有的。这回我们是自内向外修改库文件的,而 `client::connect` 的时候是自外向内修改的。我们需要修改 *src/lib.rs*`network` 也是公有的,如下
<span class="filename">文件名: src/lib.rs</span> <span class="filename">文件名: src/lib.rs</span>
@ -151,11 +164,14 @@ pub mod network;
现在编译的话,那个警告就消失了: 现在编译的话,那个警告就消失了:
```text ```text
warning: function is never used: `connect`, #[warn(dead_code)] on by default warning: function is never used: `connect`
--> src/network/server.rs:1:1 --> src/network/server.rs:1:1
| |
1 | fn connect() { 1 | / fn connect() {
| ^ 2 | | }
| |_^
|
= note: #[warn(dead_code)] on by default
``` ```
只剩一个警告了!尝试自食其力修改它吧! 只剩一个警告了!尝试自食其力修改它吧!
@ -169,7 +185,7 @@ warning: function is never used: `connect`, #[warn(dead_code)] on by default
### 私有性示例 ### 私有性示例
让我们看看更多例子作为练习。创建一个新的库项目并在新项目的 *src/lib.rs* 输入示例 7-5 中的代码: 让我们看看更多私有性的例子作为练习。创建一个新的库项目并在新项目的 *src/lib.rs* 输入示例 7-6 中的代码:
<span class="filename">文件名: src/lib.rs</span> <span class="filename">文件名: src/lib.rs</span>
@ -194,7 +210,7 @@ fn try_me() {
} }
``` ```
<span class="caption">示例 7-5:私有和公有函数的例子,其中部分是不正确的</span> <span class="caption">示例 7-6:私有和公有函数的例子,其中部分是不正确的</span>
在尝试编译这些代码之前,猜测一下 `try_me` 函数的哪一行会出错。接着编译项目来看看是否猜对了,然后继续阅读后面关于错误的讨论! 在尝试编译这些代码之前,猜测一下 `try_me` 函数的哪一行会出错。接着编译项目来看看是否猜对了,然后继续阅读后面关于错误的讨论!
@ -218,4 +234,4 @@ fn try_me() {
请随意设计更多的实验并尝试理解它们! 请随意设计更多的实验并尝试理解它们!
接下来,让我们讨论一下使用 `use` 关键字将模块引入作用域。 接下来,让我们讨论一下使用 `use` 关键字将项引入作用域。

View File

@ -1,10 +1,10 @@
## 导入命名 ## 引用不同模块中的名称
> [ch07-03-importing-names-with-use.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch07-03-importing-names-with-use.md) > [ch07-03-importing-names-with-use.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch07-03-importing-names-with-use.md)
> <br> > <br>
> commit d06a6a181fd61704cbf7feb55bc61d518c6469f9 > commit 550c8ea6f74060ff1f7b67e7e1878c4da121682d
我们已经讲到了如何使用模块名称作为调用的一部分,来调用模块中的函数,如示例 7-6 中所示的 `nested_modules` 函数调用。 我们已经讲到了如何使用模块名称作为调用的一部分,来调用模块中的函数,如示例 7-7 中所示的 `nested_modules` 函数调用。
<span class="filename">文件名: src/main.rs</span> <span class="filename">文件名: src/main.rs</span>
@ -22,13 +22,13 @@ fn main() {
} }
``` ```
<span class="caption">示例 7-6:通过完全指定模块中的路径来调用函数</span> <span class="caption">示例 7-7:通过完全指定模块中的路径来调用函数</span>
如你所见,指定函数的完全限定名称可能会非常冗长。所幸 Rust 有一个关键字使得这些调用显得更简洁。 如你所见,指定函数的完全限定名称可能会非常冗长。所幸 Rust 有一个关键字使得这些调用显得更简洁。
### 使用 `use` 的简单导入 ### 使用 `use` 关键字将名称导入作用域
Rust 的 `use` 关键字的工作是缩短冗长的函数调用,通过将想要调用的函数所在的模块引入到作用域中。这是一个将 `a::series::of` 模块导入一个二进制 crate 的根作用域的例子: Rust 的 `use` 关键字的工作通过将想要调用的函数所在的模块引入到作用域中来缩短冗长的函数调用。这是一个将 `a::series::of` 模块导入一个二进制 crate 的根作用域的例子:
<span class="filename">文件名: src/main.rs</span> <span class="filename">文件名: src/main.rs</span>
@ -72,7 +72,7 @@ fn main() {
这使得我们可以忽略所有的模块并直接引用函数。 这使得我们可以忽略所有的模块并直接引用函数。
因为枚举也像模块一样组成了某种命名空间,也可以使用 `use` 来导入枚举的成员。对于任何类型的 `use` 语句,如果从一个命名空间导入多个项,可以使用大括号和逗号来列举它们,像这样: 因为枚举也像模块一样组成了某种命名空间,也可以使用 `use` 来导入枚举的成员。对于任何类型的 `use` 语句,如果从一个命名空间导入多个项,可以在最后使用大括号和逗号来列举它们,像这样:
```rust ```rust
enum TrafficLight { enum TrafficLight {
@ -90,9 +90,11 @@ fn main() {
} }
``` ```
### 使用 `*` 的全局引用导入 我们仍然为 `Green` 成员指定了 `TrafficLight` 命名空间,因为并没有在 `use` 语句中包含 `Green`
为了一次导入某个命名空间的所有项,可以使用 `*` 语法。例如: ### 使用 glob 将所有名称引入作用域
为了一次将某个命名空间下的所有名称都引入作用域,可以使用 `*` 语法,这称为 **glob 运算符***glob operator*)。这个例子将一个枚举的所有成员引入作用域而没有将其一一列举出来:
```rust ```rust
enum TrafficLight { enum TrafficLight {
@ -110,7 +112,7 @@ fn main() {
} }
``` ```
`*` 被称为 **全局导入***glob*),它会导入命名空间中所有可见的项。全局导入应该保守的使用:它们是方便的,但是也可能会引入多于预期的内容从而导致命名冲突。 `*` 会将 `TrafficLight` 命名空间中所有可见的项都引入作用域。请保守的使用 glob:它们是方便的,但是也可能会引入多于预期的内容从而导致命名冲突。
### 使用 `super` 访问父模块 ### 使用 `super` 访问父模块
@ -132,7 +134,7 @@ mod tests {
} }
``` ```
第十一章会更详细的解释测试,不过其部分内容现在应该可以理解了:有一个叫做 `tests` 的模块紧邻其他模块,同时包含一个叫做 `it_works` 的函数。即便存在一些特殊注解,`tests` 也不过是另外一个模块!所以我们的模块层次结构看起来像这样: 第十一章会更详细的解释测试,不过其部分内容现在应该可以理解了:有一个叫做 `tests` 的模块紧邻其他模块,同时包含一个叫做 `it_works` 的函数。即便存在一些特殊注解,`tests` 也不过是另外一个模块!所以我们的模块层次结构看起来像这样:
```text ```text
communicator communicator
@ -142,7 +144,7 @@ communicator
└── tests └── tests
``` ```
测试是为了检验库中的代码而存在的,所以让我们尝试在 `it_works` 函数中调用 `client::connect` 函数,即便现在不准备测试任何功能: 测试是为了检验库中的代码而存在的,所以让我们尝试在 `it_works` 函数中调用 `client::connect` 函数,即便现在不准备测试任何功能。这还不能工作
<span class="filename">文件名: src/lib.rs</span> <span class="filename">文件名: src/lib.rs</span>
@ -165,7 +167,7 @@ error[E0433]: failed to resolve. Use of undeclared type or module `client`
--> src/lib.rs:9:9 --> src/lib.rs:9:9
| |
9 | client::connect(); 9 | client::connect();
| ^^^^^^^^^^^^^^^ Use of undeclared type or module `client` | ^^^^^^ Use of undeclared type or module `client`
``` ```
编译失败了,不过为什么呢?并不需要像 *src/main.rs* 那样将 `communicator::` 置于函数前,因为这里肯定是在 `communicator` 库 crate 之内的。失败的原因是路径是相对于当前模块的,在这里就是 `tests`。唯一的例外就是 `use` 语句,它默认是相对于 crate 根模块的。我们的 `tests` 模块需要 `client` 模块位于其作用域中! 编译失败了,不过为什么呢?并不需要像 *src/main.rs* 那样将 `communicator::` 置于函数前,因为这里肯定是在 `communicator` 库 crate 之内的。失败的原因是路径是相对于当前模块的,在这里就是 `tests`。唯一的例外就是 `use` 语句,它默认是相对于 crate 根模块的。我们的 `tests` 模块需要 `client` 模块位于其作用域中!
@ -176,7 +178,7 @@ error[E0433]: failed to resolve. Use of undeclared type or module `client`
::client::connect(); ::client::connect();
``` ```
要么可以使用 `super` 在层级中获取当前模块的上一级模块 要么可以使用 `super` 在层级中上移到当前模块的上一级模块,如下
```rust,ignore ```rust,ignore
super::client::connect(); super::client::connect();
@ -212,7 +214,7 @@ $ cargo test
running 1 test running 1 test
test tests::it_works ... ok test tests::it_works ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
``` ```
## 总结 ## 总结

View File

@ -2,12 +2,12 @@
> [ch08-00-common-collections.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch08-00-common-collections.md) > [ch08-00-common-collections.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch08-00-common-collections.md)
> <br> > <br>
> commit d06a6a181fd61704cbf7feb55bc61d518c6469f9 > commit 54e81980185fbb1a4cb5a18dce1dc6deeb66b573
Rust 标准库中包含一系列被称为 **集合***collections*)的非常有用的数据结构。大部分其他数据类型都代表一个特定的值,不过集合可以包含多个值。不同于内建的数组和元组类型,这些集合指向的数据是储存在堆上的,这意味着数据的数量不必在编译时就知并且可以随着程序的运行增长或缩小。每种集合都有着不同能力和代价,而为所处的场景选择合适的集合则是你将要始终发展的技能。在这一章里,我们将详细的了解三个在 Rust 程序中被广泛使用的集合: Rust 标准库中包含一系列被称为 **集合***collections*)的非常有用的数据结构。大部分其他数据类型都代表一个特定的值,不过集合可以包含多个值。不同于内建的数组和元组类型,这些集合指向的数据是储存在堆上的,这意味着数据的数量不必在编译时就知并且可以随着程序的运行增长或缩小。每种集合都有着不同能力和代价,而为所处的场景选择合适的集合则是你将要始终成长的技能。在这一章里,我们将详细的了解三个在 Rust 程序中被广泛使用的集合:
* *vector* 允许我们一个挨着一个地储存一系列数量可变的值 * *vector* 允许我们一个挨着一个地储存一系列数量可变的值
* **字符串***string*)是一个字符的集合。我们之前见过 `String` 类型,现在将详细介绍它 * **字符串***string*)是一个字符的集合。我们之前见过 `String` 类型,不过在本章我们将深入了解
* **哈希 map***hash map*允许我们将值与一个特定的键key相关联。这是一个叫做 *map* 的更通用的数据结构的特定实现。 * **哈希 map***hash map*允许我们将值与一个特定的键key相关联。这是一个叫做 *map* 的更通用的数据结构的特定实现。
对于标准库提供的其他类型的集合,请查看[文档][collections]。 对于标准库提供的其他类型的集合,请查看[文档][collections]。

View File

@ -1,32 +1,36 @@
## vector ## vector 用来储存一系列的值
> [ch08-01-vectors.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch08-01-vectors.md) > [ch08-01-vectors.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch08-01-vectors.md)
> <br> > <br>
> commit 6c24544ba718bce0755bdaf03423af86280051d5 > commit 550c8ea6f74060ff1f7b67e7e1878c4da121682d
我们要讲到的第一个类型是`Vec<T>`,也被称为 *vector*。vector 允许我们在一个单独的数据结构中储存多于一个值它在内存中彼此相邻地排列所有的值。vector 只能储存相同类型的值。它们在拥有一系列项的场景下非常实用,例如文件中的文本行或是购物车中商品的价格。 我们要讲到的第一个类型是 `Vec<T>`,也被称为 *vector*。vector 允许我们在一个单独的数据结构中储存多于一个它在内存中彼此相邻地排列所有的值。vector 只能储存相同类型的值。它们在拥有一系列项的场景下非常实用,例如文件中的文本行或是购物车中商品的价格。
### 新建 vector ### 新建 vector
为了创建一个新的空 vector可以调用 `Vec::new` 函数: 为了创建一个新的空 vector可以调用 `Vec::new` 函数,如示例 8-1 所示
```rust ```rust
let v: Vec<i32> = Vec::new(); let v: Vec<i32> = Vec::new();
``` ```
注意这里我们增加了一个类型注解。因为没有向这个 vector 中插入任何值Rust 并不知道我们想要储存什么类型的元素。这是一个非常重要的点。vector 是同质的homogeneous它们可以储存很多值不过这些值必须都是相同类型的。vector 是用泛型实现的,第十章会涉及到如何对你自己的类型使用它们。现在,所有你需要知道的就是 `Vec` 是一个由标准库提供的类型,它可以存放任何类型,而当 `Vec` 存放某个特定类型时,那个类型位于尖括号中。这里我们告诉 Rust `v` 这个 `Vec` 将存放 `i32` 类型的元素。 <span class="caption">示例 8-1新建一个空的 vector 来储存 `i32` 类型的值</span>
在实际的代码中,一旦插入值 Rust 就可以推断出想要存放的类型,所以你很少会需要这些类型注解。更常见的做法是使用初始值来创建一个 `Vec`,而且为了方便 Rust 提供了 `vec!` 宏。这个宏会根据我们提供的值来创建一个新的 `Vec`。如下代码会新建一个拥有值 `1`、`2` 和 `3``Vec<i32>` 注意这里我们增加了一个类型注解。因为没有向这个 vector 中插入任何值Rust 并不知道我们想要储存什么类型的元素。这是一个非常重要的点。vector 是用泛型实现的,第十章会涉及到如何对你自己的类型使用它们。现在,所有你需要知道的就是 `Vec` 是一个由标准库提供的类型,它可以存放任何类型,而当 `Vec` 存放某个特定类型时,那个类型位于尖括号中。这里我们告诉 Rust `v` 这个 `Vec` 将存放 `i32` 类型的元素。
在更实际的代码中,一旦插入值 Rust 就可以推断出想要存放的类型,所以你很少会需要这些类型注解。更常见的做法是使用初始值来创建一个 `Vec`,而且为了方便 Rust 提供了 `vec!` 宏。这个宏会根据我们提供的值来创建一个新的 `Vec`。示例 8-2 新建一个拥有值 `1`、`2` 和 `3``Vec<i32>`
```rust ```rust
let v = vec![1, 2, 3]; let v = vec![1, 2, 3];
``` ```
<span class="caption">示例 8-2新建一个包含初值的 vector</span>
因为我们提供了 `i32` 类型的初始值Rust 可以推断出 `v` 的类型是 `Vec<i32>`,因此类型注解就不是必须的。接下来让我们看看如何修改一个 vector。 因为我们提供了 `i32` 类型的初始值Rust 可以推断出 `v` 的类型是 `Vec<i32>`,因此类型注解就不是必须的。接下来让我们看看如何修改一个 vector。
### 更新 vector ### 更新 vector
对于新建一个 vector 并向其增加元素,可以使用 `push` 方法: 对于新建一个 vector 并向其增加元素,可以使用 `push` 方法,如示例 8-3 所示
```rust ```rust
let mut v = Vec::new(); let mut v = Vec::new();
@ -37,11 +41,13 @@ v.push(7);
v.push(8); v.push(8);
``` ```
如第三章中讨论的任何变量一样,如果想要能够改变它的值,必须使用 `mut` 关键字使其可变。放入其中的所有值都是 `i32` 类型的,而且 Rust 也根据数据如此判断,所以不需要 `Vec<i32>` 注解。 <span class="caption">示例 8-3使用 `push` 方法向 vector 增加值</span>
如第三章中讨论的任何变量一样,如果想要能够改变它的值,必须使用 `mut` 关键字使其可变。放入其中的所有值都是 `i32` 类型的,而且 Rust 也根据数据做出如此判断,所以不需要 `Vec<i32>` 注解。
### 丢弃 vector 时也会丢弃其所有元素 ### 丢弃 vector 时也会丢弃其所有元素
类似于任何其他的 `struct`vector 在其离开作用域时会被释放: 类似于任何其他的 `struct`vector 在其离开作用域时会被释放,如示例 8-4 所标注的
```rust ```rust
{ {
@ -52,13 +58,15 @@ v.push(8);
} // <- v goes out of scope and is freed here } // <- v goes out of scope and is freed here
``` ```
<span class="caption">示例 8-4展示 vector 和其元素于何处被丢弃</span>
当 vector 被丢弃时,所有其内容也会被丢弃,这意味着这里它包含的整数将被清理。这可能看起来非常直观,不过一旦开始使用 vector 元素的引用,情况就变得有些复杂了。下面让我们处理这种情况! 当 vector 被丢弃时,所有其内容也会被丢弃,这意味着这里它包含的整数将被清理。这可能看起来非常直观,不过一旦开始使用 vector 元素的引用,情况就变得有些复杂了。下面让我们处理这种情况!
### 读取 vector 的元素 ### 读取 vector 的元素
现在你知道如何创建、更新和销毁 vector 了,接下来的一步最好了解一下如何读取它们的内容。有两种方法引用 vector 中储存的值。为了更加清楚的说明这个例子,我们标注这些函数返回的值的类型。 现在你知道如何创建、更新和销毁 vector 了,接下来的一步最好了解一下如何读取它们的内容。有两种方法引用 vector 中储存的值。为了更加清楚的说明这个例子,我们标注这些函数返回的值的类型。
这个例子展示了访问 vector 中一个值的两种方式,索引语法或者 `get` 方法: 示例 8-5 展示了访问 vector 中一个值的两种方式,索引语法或者 `get` 方法:
```rust ```rust
let v = vec![1, 2, 3, 4, 5]; let v = vec![1, 2, 3, 4, 5];
@ -67,9 +75,11 @@ let third: &i32 = &v[2];
let third: Option<&i32> = v.get(2); let third: Option<&i32> = v.get(2);
``` ```
这里有一些需要注意的地方。首先,我们使用索引值 `2` 来获取第三个元素,索引是从 0 开始的。其次,这两个不同的获取第三个元素的方式分别为:使用 `&``[]` 返回一个引用;或者使用 `get` 方法以索引作为参数来返回一个 `Option<&T>` <span class="caption">列表 8-5使用索引语法或 `get` 方法来访问 vector 中的项</span>
Rust 有两个引用元素的方法的原因是程序可以选择如何处理当索引值在 vector 中没有对应值的情况。例如如下情况,如果有一个有五个元素的 vector 接着尝试访问索引为 100 的元素,程序该如何处理: 这里有两个需要注意的地方。首先,我们使用索引值 `2` 来获取第三个元素,索引是从 0 开始的。其次,这两个不同的获取第三个元素的方式分别为:使用 `&``[]` 返回一个引用;或者使用 `get` 方法以索引作为参数来返回一个 `Option<&T>`
Rust 有两个引用元素的方法的原因是程序可以选择如何处理当索引值在 vector 中没有对应值的情况。作为一个例子,让我们看看如果有一个有五个元素的 vector 接着尝试访问索引为 100 的元素时程序会如何处理,如示例 8-6 所示:
```rust,should_panic ```rust,should_panic
let v = vec![1, 2, 3, 4, 5]; let v = vec![1, 2, 3, 4, 5];
@ -78,13 +88,15 @@ let does_not_exist = &v[100];
let does_not_exist = v.get(100); let does_not_exist = v.get(100);
``` ```
<span class="caption">示例 8-6尝试访问一个包含 5 个元素的 vector 的索引 100 处的元素</span>
当运行这段代码,你会发现对于第一个 `[]` 方法,当引用一个不存在的元素时 Rust 会造成 `panic!`。这个方法更适合当程序认为尝试访问超过 vector 结尾的元素是一个严重错误的情况,这时应该使程序崩溃。 当运行这段代码,你会发现对于第一个 `[]` 方法,当引用一个不存在的元素时 Rust 会造成 `panic!`。这个方法更适合当程序认为尝试访问超过 vector 结尾的元素是一个严重错误的情况,这时应该使程序崩溃。
`get` 方法被传递了一个数组外的索引时,它不会 panic 而是返回 `None`。当偶尔出现超过 vector 范围的访问属于正常情况的时候可以考虑使用它。接着你的代码可以有处理 `Some(&element)``None` 的逻辑,如第六章讨论的那样。例如,索引可能来源于用户输入的数字。如果它们不慎输入了一个过大的数字那么程序就会得到 `None` 值,你可以告诉用户 `Vec` 当前元素的数量并再请求它们输入一个有效的值。这就比因为输入错误而使程序崩溃要友好的多! `get` 方法被传递了一个数组外的索引时,它不会 panic 而是返回 `None`。当偶尔出现超过 vector 范围的访问属于正常情况的时候可以考虑使用它。接着你的代码可以有处理 `Some(&element)``None` 的逻辑,如第六章讨论的那样。例如,索引可能来源于用户输入的数字。如果它们不慎输入了一个过大的数字那么程序就会得到 `None` 值,你可以告诉用户当前 vector 元素的数量并再请求它们输入一个有效的值。这就比因为输入错误而使程序崩溃要友好的多!
#### 无效引用 #### 无效引用
一旦程序获取了一个有效的引用,借用检查器将会执行第四章讲到的所有权和借用规则来确保 vector 内容的这个引用和任何其他引用保持有效。回忆一下不能在相同作用域中同时存在可变和不可变引用的规则。这个规则适用于这个例子,当我们获取了 vector 的第一个元素的不可变引用并尝试在 vector 末尾增加一个元素的时候: 一旦程序获取了一个有效的引用,借用检查器将会执行第四章讲到的所有权和借用规则来确保 vector 内容的这个引用和任何其他引用保持有效。回忆一下不能在相同作用域中同时存在可变和不可变引用的规则。这个规则适用于示例 8-7,当我们获取了 vector 的第一个元素的不可变引用并尝试在 vector 末尾增加一个元素的时候,这是行不通的
```rust,ignore ```rust,ignore
let mut v = vec![1, 2, 3, 4, 5]; let mut v = vec![1, 2, 3, 4, 5];
@ -94,30 +106,59 @@ let first = &v[0];
v.push(6); v.push(6);
``` ```
<span class="caption">示例 8-7在拥有 vector 中项的引用的同时向其增加一个元素</span>
编译会给出这个错误: 编译会给出这个错误:
```text ```text
error[E0502]: cannot borrow `v` as mutable because it is also borrowed as error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
immutable -->
| |
4 | let first = &v[0]; 4 | let first = &v[0];
| - immutable borrow occurs here | - immutable borrow occurs here
5 | 5 |
6 | v.push(6); 6 | v.push(6);
| ^ mutable borrow occurs here | ^ mutable borrow occurs here
7 | } 7 |
8 | }
| - immutable borrow ends here | - immutable borrow ends here
``` ```
这些代码看起来应该能够运行:为什么第一个元素的引用会关心 vector 结尾的变化?不能这么做的原因是由于 vector 的工作方式。在 vector 的结尾增加新元素时,在没有足够空间将所有所有元素依次相邻存放的情况下,可能会要求分配新内存并将老的元素拷贝到新的空间中。这时,第一个元素的引用就指向了被释放的内存。借用规则阻止程序陷入这种状况。 示例 8-7 中的代码看起来应该能够运行:为什么第一个元素的引用会关心 vector 结尾的变化?不能这么做的原因是由于 vector 的工作方式。在 vector 的结尾增加新元素时,在没有足够空间将所有所有元素依次相邻存放的情况下,可能会要求分配新内存并将老的元素拷贝到新的空间中。这时,第一个元素的引用就指向了被释放的内存。借用规则阻止程序陷入这种状况。
> 注意:关于更多内容,查看 Nomicon *https://doc.rust-lang.org/stable/nomicon/vec.html* > 注意:关于 `Vec<T>` 类型的更多实现细节,在 *https://doc.rust-lang.org/stable/nomicon/vec.html* 查看 “The Nomicon”
### 遍历 vector 中的元素
如果想要依次访问 vector 中的每一个元素,我们可以遍历其所有的元素而无需通过索引一次一个的访问。示例 8-8 展示了如何使用 `for` 循环来获取 `i32` 值的 vector 中的每一个元素的不可变引用并将其打印:
```rust
let v = vec![100, 32, 57];
for i in &v {
println!("{}", i);
}
```
<span class="caption">示例 8-8通过 `for` 循环遍历 vector 的元素并打印</span>
我们也可以遍历可变 vector 的每一个元素的可变引用以便能改变他们。示例 8-9 中的 `for` 循环会给每一个元素加 `50`
```rust
let mut v = vec![100, 32, 57];
for i in &mut v {
*i += 50;
}
```
<span class="caption">示例8-9遍历 vector 中元素的可变引用</span>
为了修改可变引用所指向的值,在使用 `+=` 运算符之前必须使用解引用运算符(`*`)获取 `i` 中的值。
### 使用枚举来储存多种类型 ### 使用枚举来储存多种类型
在本章的开始,我们提到 vector 只能储存相同类型的值。这是很不方便的;绝对会有需要储存一系列不同类型的值的用例。幸运的是,枚举的成员都被定义为相同的枚举类型,所以当需要在 vector 中储存不同类型值时,我们可以定义并使用一个枚举! 在本章的开始,我们提到 vector 只能储存相同类型的值。这是很不方便的;绝对会有需要储存一系列不同类型的值的用例。幸运的是,枚举的成员都被定义为相同的枚举类型,所以当需要在 vector 中储存不同类型值时,我们可以定义并使用一个枚举!
例如,假如我们想要从电子表格的一行中获取值,而这一行的有些列包含数字,有些包含浮点值,还有些是字符串。我们可以定义一个枚举,其成员会存放这些不同类型的值,同时所有这些枚举成员都会被当作相同类型,那个枚举的类型。接着可以创建一个储存枚举值的 vector这样最终就能够储存不同类型的值了 例如,假如我们想要从电子表格的一行中获取值,而这一行的有些列包含数字,有些包含浮点值,还有些是字符串。我们可以定义一个枚举,其成员会存放这些不同类型的值,同时所有这些枚举成员都会被当作相同类型,那个枚举的类型。接着可以创建一个储存枚举值的 vector这样最终就能够储存不同类型的值了。示例 8-10 展示了其用例
```rust ```rust
enum SpreadsheetCell { enum SpreadsheetCell {
@ -133,7 +174,7 @@ let row = vec![
]; ];
``` ```
<span class="caption">示例 8-1定义一个枚举以便能在 vector 中存放不同类型的数据</span> <span class="caption">示例 8-10:定义一个枚举,以便能在 vector 中存放不同类型的数据</span>
Rust 在编译时就必须准确的知道 vector 中类型的原因在于它需要知道储存每个元素到底需要多少内存。第二个好处是可以准确的知道这个 vector 中允许什么类型。如果 Rust 允许 vector 存放任意类型,那么当对 vector 元素执行操作时一个或多个类型的值就有可能会造成错误。使用枚举外加 `match` 意味着 Rust 能在编译时就保证总是会处理所有可能的情况,正如第六章讲到的那样。 Rust 在编译时就必须准确的知道 vector 中类型的原因在于它需要知道储存每个元素到底需要多少内存。第二个好处是可以准确的知道这个 vector 中允许什么类型。如果 Rust 允许 vector 存放任意类型,那么当对 vector 元素执行操作时一个或多个类型的值就有可能会造成错误。使用枚举外加 `match` 意味着 Rust 能在编译时就保证总是会处理所有可能的情况,正如第六章讲到的那样。

View File

@ -1,8 +1,8 @@
## 字符串 ## 字符串存储了 UTF-8 编码的文本
> [ch08-02-strings.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch08-02-strings.md) > [ch08-02-strings.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch08-02-strings.md)
> <br> > <br>
> commit 692c4a78aac93670bc6f1fa5d33f71ed161b9339 > commit c2fd7b2d39c4130dd17bb99c101ac94af83d1a44
第四章已经讲过一些字符串的内容,不过现在让我们更深入地了解它。字符串是新晋 Rustacean 们通常会被困住的领域。这是由于三方面内容的结合Rust 倾向于确保暴露出可能的错误,字符串是比很多程序员所想象的要更为复杂的数据结构,以及 UTF-8。所有这些结合起来对于来自其他语言背景的程序员就可能显得很困难了。 第四章已经讲过一些字符串的内容,不过现在让我们更深入地了解它。字符串是新晋 Rustacean 们通常会被困住的领域。这是由于三方面内容的结合Rust 倾向于确保暴露出可能的错误,字符串是比很多程序员所想象的要更为复杂的数据结构,以及 UTF-8。所有这些结合起来对于来自其他语言背景的程序员就可能显得很困难了。