check ch08-03

This commit is contained in:
KaiserY 2017-08-17 18:03:13 +08:00
parent 2c5097b783
commit c5a42db0bf
8 changed files with 244 additions and 253 deletions

View File

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

View File

@ -2,20 +2,20 @@
> [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>
> commit b0481ac44ff2594c6c240baa36357737739db445
> commit c6a9e77a1b1ed367e0a6d5dcd222589ad392a8ac
我们将通过使用 Cargo 创建一个新项目来开始我们的模块之旅,不过不再创建一个二进制 crate而是创建一个库 crate一个其他人可以作为依赖导入的项目。第二章我们见过的`rand`就是这样的 crate。
我们将通过使用 Cargo 创建一个新项目来开始我们的模块之旅,不过不再创建一个二进制 crate而是创建一个库 crate一个其他人可以作为依赖导入的项目。第二章猜猜看游戏中作为依赖使用的 `rand` 就是这样的 crate。
我们将创建一个提供一些通用网络功能的项目的骨架结构;我们将专注于模块和函数的组织,而不担心函数体中的具体代码。这个项目叫做 `communicator`。Cargo 默认会创建一个库 crate 除非指定其他项目类型,所以如果不像一直以来那样加入 `--bin` 参数则项目将会是一个库:
```
```text
$ cargo new communicator
$ cd communicator
```
注意 Cargo 生成了 *src/lib.rs* 而不是 *src/main.rs*。在 *src/lib.rs* 中我们会找到这些:
<span class="filename">Filename: src/lib.rs</span>
<span class="filename">文件名: src/lib.rs</span>
```rust
#[cfg(test)]
@ -26,7 +26,7 @@ mod tests {
}
```
Cargo 创建了一个空的测试来帮助我们开始库项目,不像使用`--bin`参数那样创建一个“Hello, world!”二进制项目。稍后一点会介绍`#[]`和`mod tests`语法,目前只需确保他们位于 *src/lib.rs*
Cargo 创建了一个空的测试来帮助我们开始库项目,不像使用 `--bin` 参数那样创建一个 “Hello, world!” 二进制项目。在本章之后的 “使用 `super` 访问父模块” 部分会介绍 `#[]``mod tests` 语法,目前只需确保他们位于 *src/lib.rs* 底部即可
因为没有 *src/main.rs* 文件,所以没有可供 Cargo 的 `cargo run` 执行的东西。因此,我们将使用 `cargo build` 命令只是编译库 crate 的代码。
@ -36,7 +36,7 @@ Cargo 创建了一个空的测试来帮助我们开始库项目,不像使用`-
对于 `communicator` 网络库,首先要定义一个叫做 `network` 的模块,它包含一个叫做 `connect` 的函数定义。Rust 中所有模块的定义以关键字 `mod` 开始。在 *src/lib.rs* 文件的开头在测试代码的上面增加这些代码:
<span class="filename">Filename: src/lib.rs</span>
<span class="filename">文件名: src/lib.rs</span>
```rust
mod network {
@ -49,7 +49,7 @@ mod network {
也可以在 *src/lib.rs* 文件中同时存在多个模块。例如,再拥有一个 `client` 模块,它也有一个叫做 `connect` 的函数,如列表 7-1 中所示那样增加这个模块:
<span class="filename">Filename: src/lib.rs</span>
<span class="filename">文件名: src/lib.rs</span>
```rust
mod network {
@ -63,14 +63,13 @@ mod client {
}
```
<span class="caption">Listing 7-1: The `network` module and the `client` module
defined side-by-side in *src/lib.rs*</span>
<span class="caption">列表 7-1`network` 模块和 `client` 一同定义于 *src/lib.rs*</span>
现在我们有了 `network::connect` 函数和 `client::connect` 函数。他们可能有着完全不同的功能,同时他们也不会彼此冲突,因为他们位于不同的模块。
虽然在这个例子中,我们构建了一个库,但是 *src/lib.rs* 并没有什么特殊意义。也可以在 *src/main.rs* 中使用子模块。事实上,也可以将模块放入其他模块中。这有助于随着模块的增长,将相关的功能组织在一起并又保持各自独立。如何选择组织代码依赖于如何考虑代码不同部分之间的关系。例如,对于库的用户来说,`client`模块和它的函数`connect`可能放在`network`命名空间里显得更有道理,如列表 7-2 所示:
在这个例子中,因为我们构建的是一个库,作为库入口点的文件是 *src/lib.rs*。然而,对于创建模块来说,*src/lib.rs* 并没有什么特殊意义。也可以在二进制 crate 的 *src/main.rs* 中创建模块,正如在库 crate 的 *src/lib.rs* 创建模块一样。事实上,也可以将模块放入其他模块中。这有助于随着模块的增长,将相关的功能组织在一起并又保持各自独立。如何选择组织代码依赖于如何考虑代码不同部分之间的关系。例如,对于库的用户来说,`client` 模块和它的函数 `connect` 可能放在 `network` 命名空间里显得更有道理,如列表 7-2 所示:
<span class="filename">Filename: src/lib.rs</span>
<span class="filename">文件名: src/lib.rs</span>
```rust
mod network {
@ -84,34 +83,33 @@ mod network {
}
```
<span class="caption">Listing 7-2: Moving the `client` module inside of the
`network` module</span>
<span class="caption">列表 7-2`client` 模块移动到 `network` 模块中</span>
*src/lib.rs* 文件中,将现有的`mod network`和`mod client`的定义替换为`client`模块作为`network`的一个内部模块。现在我们有了`network::connect`和`network::client::connect`函数:又一次,这两个`connect`函数也不相冲突,因为他们在不同的命名空间中。
*src/lib.rs* 文件中,将现有的 `mod network``mod client` 的定义替换为列表 7-2 中的定义,这里将 `client` 模块作为 `network` 的一个内部模块。现在我们有了 `network::connect``network::client::connect` 函数:同样的,这两个 `connect` 函数也不相冲突,因为他们在不同的命名空间中。
这样,模块之间形成了一个层次结构。*src/lib.rs* 的内容位于最顶层,而其子模块位于较低的层次。这是列表 7-1 中的例子以这种方式考虑的组织结构:
这样,模块之间形成了一个层次结构。*src/lib.rs* 的内容位于最顶层,而其子模块位于较低的层次。如下是列表 7-1 中的例子以层次的方式考虑的结构:
```
```text
communicator
├── network
└── client
```
而这是列表 7-2 中例子的的结构:
而这是列表 7-2 中例子的的层次结构:
```
```text
communicator
└── network
└── client
```
可以看到列表 7-2 中,`client`是`network`的子模块,而不是它的同级模块。更为负责的项目可以有很多的模块,所以他们需要符合逻辑地组合在一起以便记录他们。在项目中“符合逻辑”的意义全凭你的理解和库的用户对你项目领域的认识。利用我们这里讲到的技术来创建同级模块和嵌套的模块是你会喜欢的结构。
可以看到列表 7-2 中,`client` `network` 的子模块,而不是它的同级模块。更为复杂的项目可以有很多的模块,所以他们需要符合逻辑地组合在一起以便记录他们。在项目中 “符合逻辑” 的意义全凭你的理解和库的用户对你项目领域的认识。利用我们这里讲到的技术来创建同级模块和嵌套的模块,总有一个会是你会喜欢的结构。
### 将模块移动到其他文件
位于层级结构中的模块,非常类似计算机领域的另一个我们非常熟悉的结构:文件系统!我们可以利用 Rust 的模块系统连同多个文件一起分解 Rust 项目,这样就不是所有的内容都落到 *src/lib.rs* 中了。为例,我们将从列表 7-3 中的代码开始:
位于层级结构中的模块,非常类似计算机领域的另一个我们非常熟悉的结构:文件系统!我们可以利用 Rust 的模块系统连同多个文件一起分解 Rust 项目,这样就不是所有的内容都落到 *src/lib.rs* *src/main.rs* 中了。为了举例,我们将从列表 7-3 中的代码开始:
<span class="filename">Filename: src/lib.rs</span>
<span class="filename">文件名: src/lib.rs</span>
```rust
mod client {
@ -130,12 +128,11 @@ mod network {
}
```
<span class="caption">Listing 7-3: Three modules, `client`, `network`, and
`network::server`, all defined in *src/lib.rs*</span>
<span class="caption">列表 7-3三个模块`client`、`network` 和 `network::server`,他们都定义于 *src/lib.rs*</span>
这是模块层次结构:
*src/lib.rs* 文件有如下层次结构:
```
```text
communicator
├── client
└── network
@ -144,9 +141,9 @@ communicator
如果这些模块有很多函数,而这些函数又很长,将难以在文件中寻找我们需要的代码。因为这些函数被嵌套进一个或多个模块中,同时函数中的代码也会开始变长。这就有充分的理由将`client`、`network` 和 `server`每一个模块从 *src/lib.rs* 抽出并放入他们自己的文件中。
让我们开始把`client`模块提取到另一个文件中。首先,将 *src/lib.rs* 中的`client`模块代码替换为如下
首先,将 `client` 模块的代码替换为只有 `client` 模块声明,这样 *src/lib.rs* 看起来应该像这样
<span class="filename">Filename: src/lib.rs</span>
<span class="filename">文件名: src/lib.rs</span>
```rust,ignore
mod client;
@ -162,9 +159,15 @@ mod network {
}
```
这里我们仍然**定义**了`client`模块,不过去掉了大括号和`client`模块中的定义并替换为一个分号,这使得 Rust 知道去其他地方寻找模块中定义的代码。
这里我们仍然 **声明**`client` 模块,不过将代码块替换为了分号,这告诉了 Rust 在 `client` 模块的作用域中寻找另一个定义代码的位置。换句话说,`mod client;` 行意味着:
那么现在需要创建对应模块名的外部文件。在 *src/* 目录创建一个 *client.rs* 文件,接着打开它并输入如下内容,它是上一步`client`模块中被去掉的`connect`函数:
```rust,ignore
mod client {
// contents of client.rs
}
```
那么现在需要创建对应模块名的外部文件。在 *src/* 目录创建一个 *client.rs* 文件,接着打开它并输入如下内容,它是上一步被去掉的 `client` 模块中的 `connect` 函数:
<span class="filename">Filename: src/client.rs</span>
@ -175,11 +178,11 @@ fn connect() {
注意这个文件中并不需要一个 `mod` 声明;因为已经在 *src/lib.rs* 中已经使用 `mod` 声明了 `client` 模块。这个文件仅仅提供 `client` 模块的 **内容**。如果在这里加上一个 `mod client`,那么就等于给 `client` 模块增加了一个叫做 `client` 的子模块了!
Rust 默认只知道 *src/lib.rs* 中的内容。如果想要对项目加入更多文件,我们需要在 *src/lib.rs* 中告诉 Rust 去寻找其他文件;这就是为什么`mod client`需要被定义在 *src/lib.rs* 而不是在 *src/client.rs*
Rust 默认只知道 *src/lib.rs* 中的内容。如果想要对项目加入更多文件,我们需要在 *src/lib.rs* 中告诉 Rust 去寻找其他文件;这就是为什么 `mod client` 需要被定义在 *src/lib.rs* 而不能在 *src/client.rs* 的原因
现在,一切应该能成功编译,虽然会有一些警告。记住使用 `cargo build` 而不是 `cargo run` 因为这是一个库 crate 而不是二进制 crate
```
```text
$ cargo build
Compiling communicator v0.1.0 (file:///projects/communicator)
@ -202,9 +205,9 @@ warning: function is never used: `connect`, #[warn(dead_code)] on by default
| ^
```
这些警告提醒我们有从未被使用的函数。目前不用担心这些警告;在本章后面会解决他们。好消息是,他们仅仅是警告;我们的项目能够被成功编译。
这些警告提醒我们有从未被使用的函数。目前不用担心这些警告;在本章后面的 “使用 `pub` 控制可见性” 部分会解决他们。好消息是,他们仅仅是警告;我们的项目能够被成功编译。
下面使用相同的模式将`network`模块提取到自己的文件中。删除 *src/lib.rs* 中`network`模块的内容并在声明后加上一个分号,像这样:
下面使用相同的模式将 `network` 模块提取到自己的文件中。删除 *src/lib.rs* `network` 模块的内容并在声明后加上一个分号,像这样:
<span class="filename">Filename: src/lib.rs</span>
@ -273,18 +276,18 @@ note: ... or maybe `use` the module `server` instead of possibly redeclaring it
| ^^^^^^
```
<span class="caption">Listing 7-4: Error when trying to extract the `server`
submodule into *src/server.rs*</span>
<span class="caption">列表 7-4尝试将 `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 中间的记录事实上是非常有帮助的,因为它指出了一些我们还未讲到的操作:
列表 7-4 中间的 note 事实上是非常有帮助的,因为它指出了一些我们还未讲到的操作:
```
note: maybe move this module `network` to its own directory via `network/mod.rs`
```text
note: maybe move this module `network` to its own directory via
`network/mod.rs`
```
我们可以按照记录所建议的去操作,而不是继续使用之前的与模块同名文件的模式:
我们可以按照记录所建议的去操作,而不是继续使用之前的与模块同名文件的模式:
1. 新建一个叫做 *network***目录**,这是父模块的名字
2. 将 *src/network.rs* 移动到新建的 *network* 目录中并重命名,现在它是 *src/network/mod.rs*
@ -292,7 +295,7 @@ note: maybe move this module `network` to its own directory via `network/mod.rs`
如下是执行这些步骤的命令:
```sh
```text
$ mkdir src/network
$ mv src/network.rs src/network/mod.rs
$ mv src/server.rs src/network
@ -300,7 +303,7 @@ $ mv src/server.rs src/network
现在如果运行 `cargo build` 的话将顺利编译(虽然仍有警告)。现在模块的布局看起来仍然与列表 7-3 中所有代码都在 *src/lib.rs* 中时完全一样:
```
```text
communicator
├── client
└── network
@ -309,7 +312,7 @@ communicator
对应的文件布局现在看起来像这样:
```
```text
├── src
│ ├── client.rs
│ ├── lib.rs
@ -318,9 +321,9 @@ communicator
│ └── 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
communicator
├── client
└── network
@ -340,7 +343,7 @@ communicator
这些规则适用于递归(嵌套),所以如果 `foo` 模块有一个子模块 `bar``bar` 没有子模块,则 *src* 目录中应该有如下文件:
```
```text
├── foo
│ ├── bar.rs (contains the declarations in `foo::bar`)
│ └── mod.rs (contains the declarations in `foo`, including `mod bar`)

View File

@ -2,11 +2,11 @@
> [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>
> commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56
> commit 0a4ed5875aeba78a81ae03ac73aeb84d2e2aca86
我们通过将 `network``network::server` 的代码分别移动到 *src/network/mod.rs**src/network/server.rs* 文件中解决了列表 7-4 中出现的错误信息。现在,`cargo build` 能够构建我们的项目,不过仍然有一些警告信息,表示 `client::connect`、`network::connect` 和`network::server::connect` 函数没有被使用:
```
```text
warning: function is never used: `connect`, #[warn(dead_code)] on by default
src/client.rs:1:1
|
@ -26,11 +26,11 @@ warning: function is never used: `connect`, #[warn(dead_code)] on by default
| ^
```
那么为什么会出现这些错误信息呢?我们构建的是一个库,它的函数的目的是被**用户**使用,而不一定要被项目自身使用,所以不应该担心这些`connect`函数是未使用的。创建他们的意义就在于被另一个项目而不是被自己使用。
那么为什么会出现这些错误信息呢?我们构建的是一个库,它的函数的目的是被 **用户** 使用,而不一定要被项目自身使用,所以不应该担心这些 `connect` 函数是未使用的。创建他们的意义就在于被另一个项目而不是被我们自己使用。
为了理解为什么这个程序出现了这些警告,尝试作为另一个项目来使用这个 `connect` 库,从外部调用他们。为此,通过创建一个包含这些代码的 *src/main.rs* 文件,在与库 crate 相同的目录创建一个二进制 crate
<span class="filename">Filename: src/main.rs</span>
<span class="filename">文件名: src/main.rs</span>
```rust,ignore
extern crate communicator;
@ -40,7 +40,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*)。
@ -48,7 +48,7 @@ fn main() {
我们的二进制 crate 如今正好调用了库中 `client` 模块的 `connect` 函数。然而,执行 `cargo build` 会在之前的警告之后出现一个错误:
```
```text
error: module `client` is private
--> src/main.rs:4:5
|
@ -56,15 +56,15 @@ error: module `client` is private
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
```
啊哈!这告诉了我们`client`模块是私有的,这也正是那些警告的症结所在。这也是我们第一次在 Rust 上下文中涉及到**公有**和**私有**的概念。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` 模块公有:
<span class="filename">Filename: src/lib.rs</span>
<span class="filename">文件名: src/lib.rs</span>
```rust,ignore
pub mod client;
@ -74,8 +74,7 @@ mod network;
`pub` 写在 `mod` 之前。再次尝试构建:
```
<warnings>
```text
error: function `connect` is private
--> src/main.rs:4:5
|
@ -83,18 +82,18 @@ error: function `connect` is private
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
```
非常好!另一个不同的错误!好的,不同的错误信息是值得庆祝的(可能是程序员被黑的最惨的一次)。新错误表明“函数`connect`是私有的”,那么让我们修改 *src/client.rs* 将`client::connect`也设为公有:
非常好!另一个不同的错误!好的,不同的错误信息是值得庆祝的(可能是程序员被黑的最惨的一次)。新错误表明 “函数 `connect` 是私有的”,那么让我们修改 *src/client.rs* `client::connect` 也设为公有:
<span class="filename">Filename: src/client.rs</span>
<span class="filename">文件名: src/client.rs</span>
```rust
pub fn connect() {
}
```
再一次运行`cargo build`
再一次运行 `cargo build`
```
```text
warning: function is never used: `connect`, #[warn(dead_code)] on by default
--> src/network/mod.rs:1:1
|
@ -110,11 +109,11 @@ warning: function is never used: `connect`, #[warn(dead_code)] on by default
编译通过了,关于 `client::connect` 未被使用的警告消失了!
未被使用的代码并不总是意味着他们需要被设为公有的:如果你**不**希望这些函数成为公有 API 的一部分,未被使用的代码警告可能是在警告你这些代码不再需要并可以安全的删除他们。这也可能是警告你出 bug如果你刚刚不小心删除了库中所有这个函数的调用。
未被使用的代码并不总是意味着他们需要被设为公有的:如果你 **不** 希望这些函数成为公有 API 的一部分,未被使用的代码警告可能是在警告你这些代码不再需要并可以安全的删除他们。这也可能是警告你出 bug,如果你刚刚不小心删除了库中所有这个函数的调用。
当然我们的情况是,**确实** 希望另外两个函数也作为 crate 公有 API 的一部分,所以让我们也将其标记为 `pub` 并去掉剩余的警告。修改 *src/network/mod.rs* 为:
<span class="filename">Filename: src/network/mod.rs</span>
<span class="filename">文件名: src/network/mod.rs</span>
```rust,ignore
pub fn connect() {
@ -123,9 +122,9 @@ pub fn connect() {
mod server;
```
并编译:
并编译代码
```
```text
warning: function is never used: `connect`, #[warn(dead_code)] on by default
--> src/network/mod.rs:1:1
|
@ -141,8 +140,7 @@ warning: function is never used: `connect`, #[warn(dead_code)] on by default
恩,虽然将 `network::connect` 设为 `pub` 了我们仍然得到了一个未被使用函数的警告。这是因为模块中的函数是公有的,不过函数所在的 `network` 模块却不是公有的。这回我们是自内向外修改库文件的,而 `client::connect` 的时候是自外向内修改的。我们需要修改 *src/lib.rs*`network` 也是公有的:
<span class="filename">Filename: src/lib.rs</span>
<span class="filename">文件名: src/lib.rs</span>
```rust,ignore
pub mod client;
@ -150,9 +148,9 @@ pub mod client;
pub mod network;
```
现在编译的话,那个警告就消失了:
现在编译的话,那个警告就消失了:
```
```text
warning: function is never used: `connect`, #[warn(dead_code)] on by default
--> src/network/server.rs:1:1
|
@ -167,13 +165,13 @@ warning: function is never used: `connect`, #[warn(dead_code)] on by default
总的来说,有如下项的可见性规则:
1. 如果一个项是公有的,它能被任何父模块访问
2. 如果一个项是私有的,它只能被当前模块或其子模块访问
2. 如果一个项是私有的,它能被其直接父模块及其任何子模块访问
### 私有性示例
让我们看看更多例子作为练习。创建一个新的库项目并在新项目的 *src/lib.rs* 输入列表 7-5 中的代码:
<span class="filename">Filename: src/lib.rs</span>
<span class="filename">文件名: src/lib.rs</span>
```rust,ignore
mod outermost {
@ -196,8 +194,7 @@ fn try_me() {
}
```
<span class="caption">Listing 7-5: Examples of private and public functions,
some of which are incorrect</span>
<span class="caption">列表 7-5私有和公有函数的例子其中部分是不正确的</span>
在尝试编译这些代码之前,猜测一下 `try_me` 函数的哪一行会出错。接着编译项目来看看是否猜对了,然后继续阅读后面关于错误的讨论!
@ -205,11 +202,11 @@ some of which are incorrect</span>
`try_me` 函数位于项目的根模块。叫做 `outermost` 的模块是私有的,不过第二条私有性规则说明` try_me` 函数允许访问 `outermost` 模块,因为 `outermost` 位于当前(根)模块,`try_me` 也是。
`outermost::middle_function`的调用是正确的。因为`middle_function`是公有的,而`try_me`通过其父模块访问`middle_function``outermost`。根据上一段的规则我们可以确定这个模块是可访问的。
`outermost::middle_function` 的调用是正确的。因为 `middle_function` 是公有的,而 `try_me` 通过其父模块 `outermost` 访问 `middle_function`。根据上一段的规则我们可以确定这个模块是可访问的。
`outermost::middle_secret_function` 的调用会造成一个编译错误。`middle_secret_function` 是私有的,所以第二条(私有性)规则生效了。根模块既不是 `middle_secret_function` 的当前模块(`outermost`是),也不是 `middle_secret_function` 当前模块的子模块。
叫做`inside`的模块是私有的且没有子模块,所以它只能被当前模块`outermost`访问。这意味着`try_me`函数不允许调用`outermost::inside::inner_function`或`outermost::inside::secret_function`任何一个。
叫做 `inside` 的模块是私有的且没有子模块,所以它只能被当前模块 `outermost` 访问。这意味着 `try_me` 函数不允许调用 `outermost::inside::inner_function` `outermost::inside::secret_function` 中的任何一个。
#### 修改错误

View File

@ -2,11 +2,11 @@
> [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>
> commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56
> commit d06a6a181fd61704cbf7feb55bc61d518c6469f9
我们已经讲到了如何使用模块名称作为调用的一部分,来调用模块中的函数,如列表 7-6 中所示的 `nested_modules` 函数调用。
<span class="filename">Filename: src/main.rs</span>
<span class="filename">文件名: src/main.rs</span>
```rust
pub mod a {
@ -22,8 +22,7 @@ fn main() {
}
```
<span class="caption">Listing 7-6: Calling a function by fully specifying its
enclosing modules namespaces</span>
<span class="caption">列表 7-6通过完全指定模块中的路径来调用函数</span>
如你所见,指定函数的完全限定名称可能会非常冗长。所幸 Rust 有一个关键字使得这些调用显得更简洁。
@ -31,7 +30,7 @@ enclosing modules namespaces</span>
Rust 的 `use` 关键字的工作是缩短冗长的函数调用,通过将想要调用的函数所在的模块引入到作用域中。这是一个将 `a::series::of` 模块导入一个二进制 crate 的根作用域的例子:
<span class="filename">Filename: src/main.rs</span>
<span class="filename">文件名: src/main.rs</span>
```rust
pub mod a {
@ -49,7 +48,7 @@ fn main() {
}
```
`use a::series::of;`这一行的意思是每当想要引用`of`模块时,不用使用完整的`a::series::of`路径,可以直接使用`of`
`use a::series::of;` 这一行的意思是每当想要引用 `of` 模块时,不必使用完整的 `a::series::of` 路径,可以直接使用 `of`
`use` 关键字只将指定的模块引入作用域;它并不会将其子模块也引入。这就是为什么想要调用 `nested_modules` 函数时仍然必须写成 `of::nested_modules`
@ -87,7 +86,7 @@ use TrafficLight::{Red, Yellow};
fn main() {
let red = Red;
let yellow = Yellow;
let green = TrafficLight::Green; // because we didnt `use` TrafficLight::Green
let green = TrafficLight::Green;
}
```
@ -117,7 +116,7 @@ fn main() {
正如我们已经知道的,当创建一个库 crate 时Cargo 会生成一个 `tests` 模块。现在让我们来深入了解一下。在 `communicator` 项目中,打开 *src/lib.rs*
<span class="filename">Filename: src/lib.rs</span>
<span class="filename">文件名: src/lib.rs</span>
```rust,ignore
pub mod client;
@ -134,7 +133,7 @@ mod tests {
第十一章会更详细的解释测试,不过其部分内容现在应该可以理解了:有一个叫做 `tests` 的模块紧邻其他模块,同时包含一个叫做 `it_works` 的函数。即便存在一些特殊注解,`tests` 也不过是另外一个模块!所以我们的模块层次结构看起来像这样:
```
```text
communicator
├── client
├── network
@ -144,7 +143,7 @@ communicator
测试是为了检验库中的代码而存在的,所以让我们尝试在 `it_works` 函数中调用 `client::connect` 函数,即便现在不准备测试任何功能:
<span class="filename">Filename: src/lib.rs</span>
<span class="filename">文件名: src/lib.rs</span>
```rust
#[cfg(test)]
@ -158,7 +157,7 @@ mod tests {
使用 `cargo test` 命令运行测试:
```
```text
$ cargo test
Compiling communicator v0.1.0 (file:///projects/communicator)
error[E0433]: failed to resolve. Use of undeclared type or module `client`
@ -166,12 +165,6 @@ error[E0433]: failed to resolve. Use of undeclared type or module `client`
|
9 | client::connect();
| ^^^^^^^^^^^^^^^ Use of undeclared type or module `client`
warning: function is never used: `connect`, #[warn(dead_code)] on by default
--> src/network/server.rs:1:1
|
1 | fn connect() {
| ^
```
编译失败了,不过为什么呢?并不需要像 *src/main.rs* 那样将 `communicator::` 置于函数前,因为这里肯定是在 `communicator` 库 crate 之内的。之所以失败的原因是路径是相对于当前模块的,在这里就是 `tests`。唯一的例外就是 `use` 语句,它默认是相对于 crate 根模块的。我们的 `tests` 模块需要 `client` 模块位于其作用域中!
@ -194,7 +187,7 @@ super::client::connect();
为此,特别是在 `tests` 模块,`use super::something` 是常用的手段。所以现在的测试看起来像这样:
<span class="filename">Filename: src/lib.rs</span>
<span class="filename">文件名: src/lib.rs</span>
```rust
#[cfg(test)]
@ -210,7 +203,7 @@ mod tests {
如果再次运行`cargo test`,测试将会通过而且测试结果输出的第一部分将会是:
```
```text
$ cargo test
Compiling communicator v0.1.0 (file:///projects/communicator)
Running target/debug/communicator-92007ddb5330fa5a

View File

@ -2,7 +2,7 @@
> [ch08-00-common-collections.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch08-00-common-collections.md)
> <br>
> commit e6d6caab41471f7115a621029bd428a812c5260e
> commit d06a6a181fd61704cbf7feb55bc61d518c6469f9
Rust 标准库中包含一系列被称为 **集合***collections*)的非常有用的数据结构。大部分其他数据类型都代表一个特定的值,不过集合可以包含多个值。不同于内建的数组和元组类型,这些集合指向的数据是储存在堆上的,这意味着数据的数量不必在编译时就可知并且可以随着程序的运行增长或缩小。每种集合都有着不同能力和代价,而为所处的场景选择合适的集合则是你将要始终发展的技能。在这一章里,我们将详细的了解三个在 Rust 程序中被广泛使用的集合:

View File

@ -96,7 +96,7 @@ v.push(6);
编译会给出这个错误:
```
```text
error[E0502]: cannot borrow `v` as mutable because it is also borrowed as
immutable
|
@ -133,10 +133,9 @@ let row = vec![
];
```
<span class="caption">Listing 8-1: Defining an enum to be able to hold
different types of data in a vector</span>
<span class="caption">列表 8-1定义一个枚举以便能在 vector 中存放不同类型的数据</span>
Rust 在编译时就必须准确的知道 vector 中类型的原因它需要知道储存每个元素到底需要多少内存。第二个好处是可以准确的知道这个 vector 中允许什么类型。如果 Rust 允许 vector 存放任意类型,那么当对 vector 元素执行操作时一个或多个类型的值就有可能会造成错误。使用枚举外加`match`意味着 Rust 能在编译时就保证总是会处理所有可能的情况,正如第六章讲到的那样。
Rust 在编译时就必须准确的知道 vector 中类型的原因在于它需要知道储存每个元素到底需要多少内存。第二个好处是可以准确的知道这个 vector 中允许什么类型。如果 Rust 允许 vector 存放任意类型,那么当对 vector 元素执行操作时一个或多个类型的值就有可能会造成错误。使用枚举外加 `match` 意味着 Rust 能在编译时就保证总是会处理所有可能的情况,正如第六章讲到的那样。
如果在编写程序时不能确切无遗的知道运行时会储存进 vector 的所有类型,枚举技术就行不通了。相反,你可以使用 trait 对象,第十七章会讲到它。

View File

@ -2,11 +2,11 @@
> [ch08-02-strings.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch08-02-strings.md)
> <br>
> commit d362dadae60a7cc3212b107b9e9562769b0f20e3
> commit 692c4a78aac93670bc6f1fa5d33f71ed161b9339
第四章已经讲过一些字符串的内容,不过现在让我们更深入地了解一下它。字符串是新晋 Rustacean 们通常会被困住的领域。这是由于三方面内容的结合Rust 倾向于确保暴露出可能的错误,字符串是比很多程序员所想象的要更为复杂的数据结构,以及 UTF-8。所有这些结合起来对于来自其他语言背景的程序员就可能显得很困难了。
第四章已经讲过一些字符串的内容,不过现在让我们更深入地了解它。字符串是新晋 Rustacean 们通常会被困住的领域。这是由于三方面内容的结合Rust 倾向于确保暴露出可能的错误,字符串是比很多程序员所想象的要更为复杂的数据结构,以及 UTF-8。所有这些结合起来对于来自其他语言背景的程序员就可能显得很困难了。
字符串出现在集合章节的原因是,字符串是作为字节的集合外加一些方法实现的,当这些字节被解释为文本时,这些方法提供了实用的功能。在这一部分,我们会讲到`String`那些任何集合类型都有的操作,比如创建、更新和读取。也会讨论`String`于其他集合不一样的地方,例如索引`String`是很复杂的,由于人和计算机理解`String`数据的不同方式
字符串出现在集合章节的原因是,字符串是作为字节的集合外加一些方法实现的,当这些字节被解释为文本时,这些方法提供了实用的功能。在这一部分,我们会讲到 `String`那些任何集合类型都有的操作,比如创建、更新和读取。也会讨论 `String` 与其他集合不一样的地方,例如索引` String` 是很复杂的,由于人和计算机理解 `String` 数据方式的不同
### 什么是字符串?
@ -21,7 +21,7 @@ Rust 标准库中还包含一系列其他字符串类型,比如`OsString`、`O
很多 `Vec` 可用的操作在 `String` 中同样可用,从以 `new` 函数创建字符串开始,像这样:
```rust
let s = String::new();
let mut s = String::new();
```
这新建了一个叫做 `s` 的空的字符串,接着我们可以向其中装载数据。
@ -37,7 +37,7 @@ let s = data.to_string();
let s = "initial contents".to_string();
```
这会创建一个包好`initial contents`的字符串。
这会创建一个包`initial contents` 的字符串。
也可以使用 `String::from` 函数来从字符串字面值创建 `String`。如下等同于使用 `to_string`
@ -109,7 +109,7 @@ let s3 = s1 + &s2; // Note that s1 has been moved here and can no longer be used
fn add(self, s: &str) -> String {
```
这并不是标准库中实际的签名;那个`add`使用泛型定义。这里的签名使用具体类型代替了泛型,这也正是当使用`String`值调用这个方法会发生的。第十章会讨论泛型。这个签名提供了理解`+`运算那奇怪的部分的线索。
这并不是标准库中实际的签名;那个 `add` 使用泛型定义。这里我们看到的 `add` 的签名使用具体类型代替了泛型,这也正是当使用 `String` 值调用这个方法会发生的。第十章会讨论泛型。这个签名提供了理解 `+` 运算那奇怪的部分的线索。
首先,`s2` 使用了 `&`,意味着我们使用第二个字符串的 **引用** 与第一个字符串相加。这是因为 `add` 函数的 `s` 参数:只能将 `&str``String` 相加,不能将两个 `String` 值相加。不过等一下——正如 `add` 的第二个参数所指定的,`&s2` 的类型是 `&String` 而不是 `&str`。那么为什么代码还能编译呢?之所以能够在 `add` 调用中使用 `&s2` 是因为 `&String` 可以被**强转***coerced*)成 `&str`——当`add`函数被调用时Rust 使用了一个被称为 **解引用强制多态***deref coercion*)的技术,你可以将其理解为它把 `&s2` 变成了 `&s2[..]` 以供 `add` 函数使用。第十五章会更深入的讨论解引用强制多态。因为 `add` 没有获取参数的所有权,所以 `s2` 在这个操作后仍然是有效的 `String`
@ -148,7 +148,7 @@ let h = s1[0];
会导致如下错误:
```
```text
error: the trait bound `std::string::String: std::ops::Index<_>` is not
satisfied [--explain E0277]
|>
@ -182,7 +182,7 @@ let hello = "Здравствуйте";
let answer = &hello[0];
```
`answer`的值应该是什么呢?它应该是第一个字符`З`吗?当使用 UTF-8 编码时,`З`的第一个字节是`208`,第二个是`151`,所以`answer`实际上应该是`208`,不过`208`自身并不是一个有效的字母。返回`208`可不是一个请求字符串第一个字母的人所希望看到的,不过它是 Rust 在字节索引零位置所能提供的唯一数据。返回字节值可能不是人们希望看到的,即便是只有拉丁字母时:`&"hello"[0]`会返回`104`而不是`h`。为了避免返回意想不到值并造成不能立刻发现的 bug。Rust 选择不编译这些代码并及早杜绝了误会的发生。
`answer` 的值应该是什么呢?它应该是第一个字符 `З` 吗?当使用 UTF-8 编码时,`З` 的第一个字节 `208`,第二个是 `151`,所以 `answer` 实际上应该是 `208`,不过 `208` 自身并不是一个有效的字母。返回 `208` 可不是一个请求字符串第一个字母的人所希望看到的,不过它是 Rust 在字节索引零位置所能提供的唯一数据。返回字节值可能不是人们希望看到的,即便是只有拉丁字母时:`&"hello"[0]` 会返回 `104` 而不是 `h`。为了避免返回意想不到值并造成不能立刻发现的 bug。Rust 选择不编译这些代码并及早杜绝了误会的发生。
#### 字节、标量值和字形簇!天呐!
@ -190,20 +190,20 @@ let answer = &hello[0];
比如这个用梵文书写的印度语单词 “नमस्ते”,最终它储存在 `Vec` 中的 `u8` 值看起来像这样:
```
```text
[224, 164, 168, 224, 164, 174, 224, 164, 184, 224, 165, 141, 224, 164, 164,
224, 165, 135]
```
这里有 18 个字节,也就是计算机最终会储存的数据。如果从 Unicode 标量值的角度理解他们,也就像 Rust 的 `char` 类型那样,这些字节看起来像这样:
```
```text
['न', 'म', 'स', '्', 'त', 'े']
```
这里有六个 `char`,不过第四个和第六个都不是字母,他们是发音符号本身并没有任何意义。最后,如果以字形簇的角度理解,就会得到人们所说的构成这个单词的四个字母:
```
```text
["न", "म", "स्", "ते"]
```
@ -225,7 +225,6 @@ let s = &hello[0..4];
如果获取 `&hello[0..1]` 会发生什么呢?答案是:在运行时会 panic就跟访问 vector 中的无效索引时一样:
```text
thread 'main' panicked at 'index 0 and/or 1 in `Здравствуйте` do not lie on
character boundary', ../src/libcore/str/mod.rs:1694
@ -247,7 +246,7 @@ for c in "नमस्ते".chars() {
这些代码会打印出如下内容:
```
```text
@ -258,7 +257,6 @@ for c in "नमस्ते".chars() {
`bytes` 方法返回每一个原始字节,这可能会适合你的使用场景:
```rust
for b in "नमस्ते".bytes() {
println!("{}", b);
@ -267,7 +265,7 @@ for b in "नमस्ते".bytes() {
这些代码会打印出组成 `String` 的 18 个字节,开头是这样的:
```
```text
224
164
168

View File

@ -2,17 +2,17 @@
> [ch08-03-hash-maps.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch08-03-hash-maps.md)
> <br>
> commit 4f2dc564851dc04b271a2260c834643dfd86c724
> commit d06a6a181fd61704cbf7feb55bc61d518c6469f9
最后介绍的常用集合类型是 **哈希 map***hash map*)。`HashMap<K, V>` 类型储存了一个键类型 `K` 对应一个值类型 `V` 的映射。它通过一个 **哈希函数***hashing function*来实现映射决定如何将键和值放入内存中。很多编程语言支持这种数据结构不过通常有不同的名字哈希、map、对象、哈希表或者关联数组仅举几例。
哈希 map 可以用于需要任何类型作为键来寻找数据的情况,而不是像 vector 那样通过索引。例如,在一个游戏中,你可以将每个团队的分数记录到哈希 map 中,其中键是队伍的名字而值是每个队伍的分数。给出一个队名,就能得到他们的得分。
本章我们会介绍哈希 map 的基本 API不过还有更多吸引人的功能隐藏于标准库中的`HashMap`定义的函数中。请一如既往地查看标准库文档来了解更多信息。
本章我们会介绍哈希 map 的基本 API不过还有更多吸引人的功能隐藏于标准库中 `HashMap` 定义的函数中。请一如既往地查看标准库文档来了解更多信息。
### 新建一个哈希 map
可以使用`new`创建一个空的`HashMap`,并使用`insert`来增加元素。这里我们记录两支队伍的分数,分别是蓝队和黄队。蓝队开始有 10 分而黄队开始有 50 分:
可以使用 `new` 创建一个空的 `HashMap`,并使用 `insert` 增加元素。这里我们记录两支队伍的分数,分别是蓝队和黄队。蓝队开始有 10 分而黄队开始有 50 分:
```rust
use std::collections::HashMap;
@ -37,7 +37,7 @@ let initial_scores = vec![10, 50];
let scores: HashMap<_, _> = teams.iter().zip(initial_scores.iter()).collect();
```
这里`HashMap<_, _>`类型注解是必要的,因为可能`collect`进很多不同的数据结构,而除非显式指定 Rust 无从得知你需要的类型。但是对于键和值的类型参数来说,可以使用下划线占位,而 Rust 能够根据 vector 中数据的类型推断出 `HashMap` 所包含的类型。
这里 `HashMap<_, _>` 类型注解是必要的,因为可能 `collect` 很多不同的数据结构,而除非显式指定否则 Rust 无从得知你需要的类型。但是对于键和值的类型参数来说,可以使用下划线占位,而 Rust 能够根据 vector 中数据的类型推断出 `HashMap` 所包含的类型。
### 哈希 map 和所有权
@ -93,14 +93,15 @@ for (key, value) in &scores {
这会以任意顺序打印出每一个键值对:
```
```text
Yellow: 50
Blue: 10
```
### 更新哈希 map
尽管键值对的数量是可以增长的,不过任何时候,每个键只能关联一个值。当你想要改变哈希 map 中的数据时,根据目标键是否有值以及值的更新策略分成多种情况,下面我们了解一下:
尽管键值对的数量是可以增长的,不过任何时候,每个键只能关联一个值。当我们想要改变哈希 map 中的数据时,必须决定如何处理一个键已经有值了的情况。可以选择完全无视旧值并用新值代替旧值。可以选择保留旧值而忽略新值,并只在键 **没有** 对应值时增加新值。或者可以结合新旧两值。让我们看看这分别该如何处理!
#### 覆盖一个值
如果我们插入了一个键值对,接着用相同的键插入一个不同的值,与这个键相关联的旧值将被替换。即便下面的代码调用了两次 `insert`,哈希 map 也只会包含一个键值对,因为两次都是对蓝队的键插入的值: