mirror of
https://github.com/KaiserY/trpl-zh-cn
synced 2025-05-25 03:18:39 +08:00
Compare commits
5 Commits
51c254a36e
...
3322fa2860
Author | SHA1 | Date | |
---|---|---|---|
|
3322fa2860 | ||
|
e759bcf994 | ||
|
7271db8751 | ||
|
b0f47574ac | ||
|
d1e0ab6569 |
@ -40,7 +40,7 @@
|
||||
- [使用包、Crate 和模块管理不断增长的项目](ch07-00-managing-growing-projects-with-packages-crates-and-modules.md)
|
||||
- [包和 Crate](ch07-01-packages-and-crates.md)
|
||||
- [定义模块来控制作用域与私有性](ch07-02-defining-modules-to-control-scope-and-privacy.md)
|
||||
- [引用模块项目的路径](ch07-03-paths-for-referring-to-an-item-in-the-module-tree.md)
|
||||
- [引用模块树中项的路径](ch07-03-paths-for-referring-to-an-item-in-the-module-tree.md)
|
||||
- [使用 `use` 关键字将路径引入作用域](ch07-04-bringing-paths-into-scope-with-the-use-keyword.md)
|
||||
- [将模块拆分成多个文件](ch07-05-separating-modules-into-different-files.md)
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
当你编写大型程序时,组织代码显得尤为重要。通过对相关功能进行分组和划分不同功能的代码,你可以清楚在哪里可以找到实现了特定功能的代码,以及在哪里可以改变一个功能的工作方式。
|
||||
|
||||
到目前为止,我们编写的程序都在一个文件的一个模块中。伴随着项目的增长,你应该通过将代码分解为多个模块和多个文件来组织代码。一个包(package)可以包含多个二进制 crate 项和一个可选的库 crate 。伴随着包的增长,你可以将包中的部分代码提取出来,做成独立的 crate,这些 crate 则作为外部依赖项。本章将会涵盖所有这些概念。对于一个由一系列相互关联的包组成的超大型项目,Cargo 提供了**工作空间**(*workspaces*)这一功能,我们将在第十四章的 [“Cargo Workspaces”][workspaces] 对此进行讲解。
|
||||
到目前为止,我们编写的程序都在一个文件的一个模块中。伴随着项目的增长,你应该通过将代码分解为多个模块和多个文件来组织代码。一个包(package)可以包含多个二进制 crate 项和一个可选的库 crate。伴随着包的增长,你可以将包中的部分代码提取出来,做成独立的 crate,这些 crate 则作为外部依赖项。本章将会涵盖所有这些概念。对于一个由一系列相互关联的包组成的超大型项目,Cargo 提供了**工作空间**(*workspaces*)这一功能,我们将在第十四章的 [“Cargo Workspaces”][workspaces] 对此进行讲解。
|
||||
|
||||
我们也会讨论封装来实现细节,这可以让你在更高层面重用代码:你实现了一个操作后,其他的代码可以通过该代码的公共接口来进行调用,而不需要知道它是如何实现的。你在编写代码时可以定义哪些部分是其他代码可以使用的公共部分,以及哪些部分是你有权更改实现细节的私有部分。这是另一种减少你在脑海中记住项目内容数量的方法。
|
||||
|
||||
|
@ -13,7 +13,7 @@ crate 有两种形式:二进制 crate 和库 crate。**二进制 crate**(*Bi
|
||||
|
||||
*crate root* 是一个源文件,Rust 编译器以它为起始点,并构成你的 crate 的根模块(我们将在 [“定义模块来控制作用域与私有性”][modules] 一节深入解读)。
|
||||
|
||||
*包*(*package*)是提供一系列功能的一个或者多个 crate的捆绑。一个包会包含一个 *Cargo.toml* 文件,阐述如何去构建这些 crate。Cargo 实际上就是一个包,它包含了用于构建你代码的命令行工具的二进制 crate。其他项目也依赖 Cargo 库来实现与 Cargo 命令行程序一样的逻辑。
|
||||
*包*(*package*)是提供一系列功能的一个或者多个 crate 的捆绑。一个包会包含一个 *Cargo.toml* 文件,阐述如何去构建这些 crate。Cargo 实际上就是一个包,它包含了用于构建你代码的命令行工具的二进制 crate。其他项目也依赖 Cargo 库来实现与 Cargo 命令行程序一样的逻辑。
|
||||
|
||||
包中可以包含至多一个库 crate(library crate)。包中可以包含任意多个二进制 crate(binary crate),但是必须至少包含一个 crate(无论是库的还是二进制的)。
|
||||
|
||||
|
@ -37,7 +37,7 @@ backyard
|
||||
└── main.rs
|
||||
```
|
||||
|
||||
这个例子中的 crate 根文件是*src/main.rs*,该文件包括了:
|
||||
这个例子中的 crate 根文件是 *src/main.rs*,该文件包含了:
|
||||
|
||||
<span class="filename">文件名:src/main.rs</span>
|
||||
|
||||
@ -45,7 +45,7 @@ backyard
|
||||
{{#rustdoc_include ../listings/ch07-managing-growing-projects/quick-reference-example/src/main.rs}}
|
||||
```
|
||||
|
||||
`pub mod garden;`行告诉编译器应该包含在*src/garden.rs*文件中发现的代码:
|
||||
`pub mod garden;` 行告诉编译器将 *src/garden.rs* 中发现的代码包含进来:
|
||||
|
||||
<span class="filename">文件名:src/garden.rs</span>
|
||||
|
||||
@ -53,21 +53,23 @@ backyard
|
||||
{{#rustdoc_include ../listings/ch07-managing-growing-projects/quick-reference-example/src/garden.rs}}
|
||||
```
|
||||
|
||||
在此处, `pub mod vegetables;`意味着在*src/garden/vegetables.rs*中的代码也应该被包括。这些代码是:
|
||||
在此处,`pub mod vegetables;` 意味着在 *src/garden/vegetables.rs* 中的代码也应该被包含。这些代码是:
|
||||
|
||||
```rust,noplayground,ignore
|
||||
{{#rustdoc_include ../listings/ch07-managing-growing-projects/quick-reference-example/src/garden/vegetables.rs}}
|
||||
```
|
||||
|
||||
现在让我们深入了解这些规则的细节并在实际中演示它们!
|
||||
现在让我们深入了解这些规则的细节并在实践中演示它们!
|
||||
|
||||
### 在模块中对相关代码进行分组
|
||||
|
||||
*模块* 让我们可以将一个 crate 中的代码进行分组,以提高可读性与重用性。因为一个模块中的代码默认是私有的,所以还可以利用模块控制项的 *私有性*。私有项是不可为外部使用的内在详细实现。我们也可以将模块和它其中的项标记为公开的,这样,外部代码就可以使用并依赖于它们。
|
||||
**模块**让我们可以将一个 crate 中的代码进行分组,以提高可读性与重用性。因为一个模块中的代码默认是私有的,所以还可以利用模块控制项的**私有性**(*privacy*)。私有项是不可为外部使用的内在详细实现。我们也可以将模块和它其中的项标记为公开的,这样,外部代码就可以使用并依赖于它们。
|
||||
|
||||
在餐饮业,餐馆中会有一些地方被称之为 *前台*(*front of house*),还有另外一些地方被称之为 *后台*(*back of house*)。前台是招待顾客的地方,在这里,店主可以为顾客安排座位,服务员接受顾客下单和付款,调酒师会制作饮品。后台则是由厨师工作的厨房,洗碗工的工作地点,以及经理做行政工作的地方组成。
|
||||
作为示例,让我们编写一个提供餐厅功能的库 `crate`。我们将定义函数的签名,但将其函数体留空以便将注意力集中在代码的组织结构上而不是餐厅实现的细节。
|
||||
|
||||
我们可以将函数放置到嵌套的模块中,来使我们的 crate 结构与实际的餐厅结构相同。通过执行 `cargo new --lib restaurant`,来创建一个新的名为 `restaurant` 的库。然后将示例 7-1 中所罗列出来的代码放入 *src/lib.rs* 中,来定义一些模块和函数。
|
||||
在餐饮业,餐馆中会有一些地方被称之为**前台**(*front of house*),还有另外一些地方被称之为**后台**(*back of house*)。前台是招待顾客的地方;这包括接待员为顾客安排座位、服务员接受点单和付款、调酒师制作饮品的地方。后台则是厨师和烹饪人员在厨房工作、洗碗工清理餐具,以及经理处理行政事务的区域。
|
||||
|
||||
为了以这种方式构建我们的 `crate`,我们可以将其功能组织到嵌套模块中。通过执行 `cargo new restaurant --lib` 来创建一个新的名为 `restaurant` 的库。然后将示例 7-1 中所罗列出来的代码放入 *src/lib.rs* 中,来定义一些模块和函数签名;这段代码即为前台部分。
|
||||
|
||||
<span class="filename">文件名:src/lib.rs</span>
|
||||
|
||||
@ -77,13 +79,13 @@ backyard
|
||||
|
||||
<span class="caption">示例 7-1:一个包含了其他内置了函数的模块的 `front_of_house` 模块</span>
|
||||
|
||||
我们定义一个模块,是以 `mod` 关键字为起始,然后指定模块的名字(本例中叫做 `front_of_house`),并且用花括号包围模块的主体。在模块内,我们还可以定义其他的模块,就像本例中的 `hosting` 和 `serving` 模块。模块还可以保存一些定义的其他项,比如结构体、枚举、常量、特性、或者函数。
|
||||
我们使用 `mod` 关键字来定义模块,后跟模块名(本例中叫做 `front_of_house`),并且用花括号包围模块的主体。在模块内,我们还可以定义其它的模块,就像本例中的 `hosting` 和 `serving` 模块。模块还可以保存一些定义的其它项,比如结构体、枚举、常量、trait、或者如示例 7-1 所示的函数。
|
||||
|
||||
通过使用模块,我们可以将相关的定义分组到一起,并指出它们为什么相关。程序员可以通过使用这段代码,更加容易地找到他们想要的定义,因为他们可以基于分组来对代码进行导航,而不需要阅读所有的定义。程序员向这段代码中添加一个新的功能时,他们也会知道代码应该放置在何处,可以保持程序的组织性。
|
||||
|
||||
在前面我们提到了,`src/main.rs` 和 `src/lib.rs` 叫做 crate 根。之所以这样叫它们是因为这两个文件的内容都分别在 crate 模块结构的根组成了一个名为 `crate` 的模块,该结构被称为 *模块树*(*module tree*)。
|
||||
在前面我们提到了,`src/main.rs` 和 `src/lib.rs` 叫做 crate 根。之所以这样叫它们是因为这两个文件的内容都分别在 crate 模块结构的根组成了一个名为 `crate` 的模块,该结构被称为**模块树**(*module tree*)。
|
||||
|
||||
示例 7-2 展示了示例 7-1 中的模块树的结构。
|
||||
示例 7-2 展示了示例 7-1 中模块树的结构。
|
||||
|
||||
```text
|
||||
crate
|
||||
@ -99,6 +101,6 @@ crate
|
||||
|
||||
<span class="caption">示例 7-2: 示例 7-1 中代码的模块树</span>
|
||||
|
||||
这个树展示了一些模块是如何被嵌入到另一个模块的(例如,`hosting` 嵌套在 `front_of_house` 中)。这个树还展示了一些模块是互为 *兄弟*(*siblings*)的,这意味着它们定义在同一模块中(`hosting` 和 `serving` 被一起定义在 `front_of_house` 中)。继续沿用家庭关系的比喻,如果一个模块 A 被包含在模块 B 中,我们将模块 A 称为模块 B 的 *子*(*child*),模块 B 则是模块 A 的 *父*(*parent*)。注意,整个模块树都植根于名为 `crate` 的隐式模块下。
|
||||
这个树展示了一些模块是如何被嵌入到另一个模块的(例如,`hosting` 嵌套在 `front_of_house` 中)。这个树还展示了一些模块是互为**兄弟**(*siblings*)的,这意味着它们定义在同一模块中;`hosting` 和 `serving` 被一起定义在 `front_of_house` 中。继续沿用家庭关系的比喻,如果一个模块 A 被包含在模块 B 中,我们将模块 A 称为模块 B 的 **子**(*child*)模块,模块 B 则是模块 A 的 **父**(*parent*)模块。注意,整个模块树都植根于名为 `crate` 的隐式模块下。
|
||||
|
||||
这个模块树可能会令你想起电脑上文件系统的目录树;这是一个非常恰当的类比!就像文件系统的目录,你可以使用模块来组织你的代码。并且,就像目录中的文件,我们需要一种方法来找到模块。
|
||||
|
@ -1,21 +1,22 @@
|
||||
## 引用模块项目的路径
|
||||
## 引用模块树中项的路径
|
||||
|
||||
> [ch07-03-paths-for-referring-to-an-item-in-the-module-tree.md](https://github.com/rust-lang/book/blob/main/src/ch07-03-paths-for-referring-to-an-item-in-the-module-tree.md)
|
||||
> <br>
|
||||
> commit 2b4565662d1a7973d870744a923f58f8f7dcce91
|
||||
<!-- https://github.com/rust-lang/book/blob/main/src/ch07-03-paths-for-referring-to-an-item-in-the-module-tree.md -->
|
||||
<!-- commit 5d22a358fb2380aa3f270d7b6269b67b8e44849e -->
|
||||
|
||||
来看一下 Rust 如何在模块树中找到一个项的位置,我们使用路径的方式,就像在文件系统使用路径一样。为了调用一个函数,我们需要知道它的路径。
|
||||
为了向 Rust 指示在模块树中从何处查找某个项,我们使用路径,就像在文件系统中使用路径一样。为了调用一个函数,我们需要知道它的路径。
|
||||
|
||||
路径有两种形式:
|
||||
|
||||
- **绝对路径**(_absolute path_)是以 crate 根(root)开头的全路径;对于外部 crate 的代码,是以 crate 名开头的绝对路径,对于当前 crate 的代码,则以字面值 `crate` 开头。
|
||||
- **相对路径**(_relative path_)从当前模块开始,以 `self`、`super` 或定义在当前模块中的标识符开头。
|
||||
- **绝对路径**(*absolute path*)是以 crate 根(root)开头的完整路径;对于外部 crate 的代码,是以 crate 名开头的绝对路径,对于当前 crate 的代码,则以字面值 `crate` 开头。
|
||||
- **相对路径**(*relative path*)从当前模块开始,以 `self`、`super` 或当前模块中的某个标识符开头。
|
||||
|
||||
绝对路径和相对路径都后跟一个或多个由双冒号(`::`)分割的标识符。
|
||||
|
||||
回到示例 7-1,假设我们希望调用 `add_to_waitlist` 函数。还是同样的问题,`add_to_waitlist` 函数的路径是什么?在示例 7-3 中删除了一些模块和函数。
|
||||
回到示例 7-1,假设我们希望调用 `add_to_waitlist` 函数。这相当于在问:`add_to_waitlist` 函数的路径是什么?在示例 7-3 中删除了示例 7-1 的一些模块和函数。
|
||||
|
||||
我们在 crate 根定义了一个新函数 `eat_at_restaurant`,并在其中展示调用 `add_to_waitlist` 函数的两种方法。`eat_at_restaurant` 函数是我们 crate 库的一个公共 API,所以我们使用 `pub` 关键字来标记它。在 [“使用 `pub` 关键字暴露路径”][pub] 一节,我们将详细介绍 `pub`。注意,这个例子无法编译通过,我们稍后会解释原因。
|
||||
我们在 crate 根定义了一个新函数 `eat_at_restaurant`,并在其中展示调用 `add_to_waitlist` 函数的两种方法。这些路径都是正确的,不过因为存在另一个问题导致示例无法照原样编译。稍后我们会解释为什么。
|
||||
|
||||
`eat_at_restaurant` 函数是我们 crate 库的一个公共 API,所以我们使用 `pub` 关键字来标记它。在 [“使用 `pub` 关键字暴露路径”][pub] 一节,我们将详细介绍 `pub`。
|
||||
|
||||
<span class="filename">文件名:src/lib.rs</span>
|
||||
|
||||
@ -25,15 +26,13 @@
|
||||
|
||||
<span class="caption">示例 7-3: 使用绝对路径和相对路径来调用 `add_to_waitlist` 函数</span>
|
||||
|
||||
第一种方式,我们在 `eat_at_restaurant` 中调用 `add_to_waitlist` 函数,使用的是绝对路径。`add_to_waitlist` 函数与 `eat_at_restaurant` 被定义在同一 crate 中,这意味着我们可以使用 `crate` 关键字为起始的绝对路径。
|
||||
第一次在 `eat_at_restaurant` 中调用 `add_to_waitlist` 函数时,使用的是绝对路径。`add_to_waitlist` 函数与 `eat_at_restaurant` 被定义在同一 crate 中,这意味着我们可以使用 `crate` 关键字为起始的绝对路径。接着我们依次包含各级模块,直到我们找到 `add_to_waitlist`。你可以想象出一个相同结构的文件系统:我们通过指定路径 `/front_of_house/hosting/add_to_waitlist` 来执行 `add_to_waitlist` 程序。我们使用 `crate` 从 crate 根开始就类似于在 shell 中使用 `/` 从文件系统根开始。
|
||||
|
||||
在 `crate` 后面,我们持续地嵌入模块,直到我们找到 `add_to_waitlist`。你可以想象出一个相同结构的文件系统,我们通过指定路径 `/front_of_house/hosting/add_to_waitlist` 来执行 `add_to_waitlist` 程序。我们使用 `crate` 从 crate 根开始就类似于在 shell 中使用 `/` 从文件系统根开始。
|
||||
第二次在 `eat_at_restaurant` 中调用 `add_to_waitlist` 时,使用的是相对路径。这个路径以 `front_of_house` 为起始,这个模块在模块树中与 `eat_at_restaurant` 定义在同一层级。与之等价的文件系统路径就是 `front_of_house/hosting/add_to_waitlist`。以模块名开头意味着该路径是相对路径。
|
||||
|
||||
第二种方式,我们在 `eat_at_restaurant` 中调用 `add_to_waitlist`,使用的是相对路径。这个路径以 `front_of_house` 为起始,这个模块在模块树中,与 `eat_at_restaurant` 定义在同一层级。与之等价的文件系统路径就是 `front_of_house/hosting/add_to_waitlist`。以模块名开头意味着该路径是相对路径。
|
||||
选择使用相对路径还是绝对路径要取决于你的项目,也取决于你是更倾向于将项的定义代码与使用该项的代码分开来移动,还是一起移动。例如,如果我们要将 `front_of_house` 模块和 `eat_at_restaurant` 函数一起移动到一个名为 `customer_experience` 的模块中,我们需要更新 `add_to_waitlist` 的绝对路径,但是相对路径还是可用的。相反,如果我们要将 `eat_at_restaurant` 函数单独移到一个名为 `dining` 的模块中,还是可以使用原本的绝对路径来调用 `add_to_waitlist`,但是相对路径必须要更新。我们更倾向于使用绝对路径,因为把代码定义和项调用各自独立地移动是更常见的。
|
||||
|
||||
选择使用相对路径还是绝对路径,要取决于你的项目,也取决于你是更倾向于将项的定义代码与使用该项的代码分开来移动,还是一起移动。举一个例子,如果我们要将 `front_of_house` 模块和 `eat_at_restaurant` 函数一起移动到一个名为 `customer_experience` 的模块中,我们需要更新 `add_to_waitlist` 的绝对路径,但是相对路径还是可用的。然而,如果我们要将 `eat_at_restaurant` 函数单独移到一个名为 `dining` 的模块中,还是可以使用原本的绝对路径来调用 `add_to_waitlist`,但是相对路径必须要更新。我们更倾向于使用绝对路径,因为把代码定义和项调用各自独立地移动是更常见的。
|
||||
|
||||
让我们试着编译一下示例 7-3,并查明为何不能编译!示例 7-4 展示了这个错误。
|
||||
让我们试着编译一下示例 7-3,并查明其为何不能编译!示例 7-4 展示了这个错误。
|
||||
|
||||
```console
|
||||
{{#include ../listings/ch07-managing-growing-projects/listing-07-03/output.txt}}
|
||||
@ -41,9 +40,9 @@
|
||||
|
||||
<span class="caption">示例 7-4: 构建示例 7-3 出现的编译器错误</span>
|
||||
|
||||
错误信息说 `hosting` 模块是私有的。换句话说,我们拥有 `hosting` 模块和 `add_to_waitlist` 函数的正确路径,但是 Rust 不让我们使用,因为它不能访问私有片段。在 Rust 中,默认所有项(函数、方法、结构体、枚举、模块和常量)对父模块都是私有的。如果希望创建一个私有函数或结构体,你可以将其放入一个模块。
|
||||
错误信息说 `hosting` 模块是私有的。换句话说,我们拥有 `hosting` 模块和 `add_to_waitlist` 函数的正确路径,但是 Rust 不让我们使用,因为它不能访问私有片段。在 Rust 中,所有项(函数、方法、结构体、枚举、模块和常量)默认对父模块都是私有的。如果希望创建一个如函数或结构体的私有项,可以将其放入一个模块。
|
||||
|
||||
父模块中的项不能使用子模块中的私有项,但是子模块中的项可以使用它们父模块中的项。这是因为子模块封装并隐藏了它们的实现详情,但是子模块可以看到它们定义的上下文。继续拿餐馆作比喻,把私有性规则想象成餐馆的后台办公室:餐馆内的事务对餐厅顾客来说是不可知的,但办公室经理可以洞悉其经营的餐厅并在其中做任何事情。
|
||||
父模块中的项不能使用子模块中的私有项,但是子模块中的项可以使用它们父模块中的项。这是因为子模块封装并隐藏了它们的实现详情,但是子模块可以看到定义它们的上下文。继续我们的比喻,把私有性规则想象成餐馆的后台办公室:后台的事务对餐厅顾客来说是不可知的,但办公室经理可以洞悉其经营的餐厅并在其中做任何事情。
|
||||
|
||||
Rust 选择以这种方式来实现模块系统功能,因此默认隐藏内部实现细节。这样一来,你就知道可以更改内部代码的哪些部分而不会破坏外部代码。不过 Rust 也确实提供了通过使用 `pub` 关键字来创建公共项,使子模块的内部部分暴露给上级模块。
|
||||
|
||||
@ -54,7 +53,7 @@ Rust 选择以这种方式来实现模块系统功能,因此默认隐藏内部
|
||||
<span class="filename">文件名:src/lib.rs</span>
|
||||
|
||||
```rust,ignore,does_not_compile
|
||||
{{#rustdoc_include ../listings/ch07-managing-growing-projects/listing-07-05/src/lib.rs}}
|
||||
{{#rustdoc_include ../listings/ch07-managing-growing-projects/listing-07-05/src/lib.rs:here}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 7-5: 使用 `pub` 关键字声明 `hosting` 模块使其可在 `eat_at_restaurant` 使用</span>
|
||||
@ -67,7 +66,7 @@ Rust 选择以这种方式来实现模块系统功能,因此默认隐藏内部
|
||||
|
||||
<span class="caption">示例 7-6: 构建示例 7-5 出现的编译器错误</span>
|
||||
|
||||
发生了什么?在 `mod hosting` 前添加了 `pub` 关键字,使其变成公有的。伴随着这种变化,如果我们可以访问 `front_of_house`,那我们也可以访问 `hosting`。但是 `hosting` 的 _内容_(_contents_)仍然是私有的;这表明使模块公有并不使其内容也是公有的。模块上的 `pub` 关键字只允许其父模块引用它,而不允许访问内部代码。因为模块是一个容器,只是将模块变为公有能做的其实并不太多;同时需要更深入地选择将一个或多个项变为公有。
|
||||
发生了什么?在 `mod hosting` 前添加了 `pub` 关键字,使其变成公有的。伴随着这种变化,如果我们可以访问 `front_of_house`,那我们也可以访问 `hosting`。但是 `hosting` 的**内容**(_contents_)仍然是私有的;这表明使模块公有并不使其内容也是公有的。模块上的 `pub` 关键字只允许其父模块引用它,而不允许访问内部代码。因为模块是一个容器,只是将模块变为公有能做的其实并不太多;同时需要更深入地选择将一个或多个项变为公有。
|
||||
|
||||
示例 7-6 中的错误说,`add_to_waitlist` 函数是私有的。私有性规则不但应用于模块,还应用于结构体、枚举、函数和方法。
|
||||
|
||||
@ -76,34 +75,32 @@ Rust 选择以这种方式来实现模块系统功能,因此默认隐藏内部
|
||||
<span class="filename">文件名:src/lib.rs</span>
|
||||
|
||||
```rust,noplayground,test_harness
|
||||
{{#rustdoc_include ../listings/ch07-managing-growing-projects/listing-07-07/src/lib.rs}}
|
||||
{{#rustdoc_include ../listings/ch07-managing-growing-projects/listing-07-07/src/lib.rs:here}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 7-7: 为 `mod hosting`
|
||||
和 `fn add_to_waitlist` 添加 `pub` 关键字使它们可以在
|
||||
`eat_at_restaurant` 函数中被调用</span>
|
||||
<span class="caption">示例 7-7: 为 `mod hosting` 和 `fn add_to_waitlist` 添加 `pub` 关键字使它们可以在 `eat_at_restaurant` 函数中被调用</span>
|
||||
|
||||
现在代码可以编译通过了!为了了解为何增加 `pub` 关键字使得我们可以在 `eat_at_restaurant` 中调用这些路径与私有性规则有关,让我们看看绝对路径和相对路径。
|
||||
|
||||
在绝对路径,我们从 `crate` 也就是 crate 根开始。crate 根中定义了 `front_of_house` 模块。虽然 `front_of_house` 模块不是公有的,不过因为 `eat_at_restaurant` 函数与 `front_of_house` 定义于同一模块中(即,`eat_at_restaurant` 和 `front_of_house` 是兄弟),我们可以从 `eat_at_restaurant` 中引用 `front_of_house`。接下来是使用 `pub` 标记的 `hosting` 模块。我们可以访问 `hosting` 的父模块,所以可以访问 `hosting`。最后,`add_to_waitlist` 函数被标记为 `pub` ,我们可以访问其父模块,所以这个函数调用是有效的!
|
||||
在绝对路径,我们从 `crate` 也就是 crate 根开始。crate 根中定义了 `front_of_house` 模块。虽然 `front_of_house` 模块不是公有的,不过因为 `eat_at_restaurant` 函数与 `front_of_house` 定义于同一级模块中(即,`eat_at_restaurant` 和 `front_of_house` 是兄弟),我们可以从 `eat_at_restaurant` 中引用 `front_of_house`。接下来是使用 `pub` 标记的 `hosting` 模块。我们可以访问 `hosting` 的父模块,所以可以访问 `hosting`。最后,`add_to_waitlist` 函数被标记为 `pub` ,我们可以访问其父模块,所以这个函数调用是有效的!
|
||||
|
||||
在相对路径,其逻辑与绝对路径相同,除了第一步:不同于从 crate 根开始,路径从 `front_of_house` 开始。`front_of_house` 模块与 `eat_at_restaurant` 定义于同一模块,所以从 `eat_at_restaurant` 中开始定义的该模块相对路径是有效的。接下来因为 `hosting` 和 `add_to_waitlist` 被标记为 `pub`,路径其余的部分也是有效的,因此函数调用也是有效的!
|
||||
在相对路径,其逻辑与绝对路径相同,除了第一步:不同于从 crate 根开始,路径从 `front_of_house` 开始。`front_of_house` 模块与 `eat_at_restaurant` 定义于同一级模块,所以从 `eat_at_restaurant` 中开始定义的该模块相对路径是有效的。接下来因为 `hosting` 和 `add_to_waitlist` 被标记为 `pub`,路径其余的部分也是有效的,因此函数调用也是有效的!
|
||||
|
||||
如果你计划共享你的库 crate 以便其它项目可以使用你的代码,公有 API 将是决定 crate 用户如何与你代码交互的契约。关于管理公有 API 的修改以便被人更容易依赖你的库有着很多考量。这些考量超出了本书的范畴;如果你对这些话题感兴趣,请查阅 [The Rust API Guidelines][api-guidelines]
|
||||
如果你计划共享你的库 crate 以便其它项目可以使用你的代码,公有 API 将是决定 crate 用户如何与你代码交互的契约。关于管理公有 API 的修改以便被人更容易依赖你的库有着很多考量。这些考量超出了本书的范畴;如果你对这些话题感兴趣,请查阅 [The Rust API Guidelines][api-guidelines]。
|
||||
|
||||
> ### 二进制和库 crate 包的最佳实践
|
||||
>
|
||||
> 我们提到过包(package)可以同时包含一个 *src/main.rs* 二进制 crate 根和一个 *src/lib.rs* 库 crate 根,并且这两个 crate 默认以包名来命名。通常,这种包含二进制 crate 和库 crate 的模式的包,在二进制 crate 中只保留足以生成一个可执行文件的代码,并由可执行文件调用库 crate 的代码。又因为库 crate 可以共享,这使得其它项目从包提供的大部分功能中受益。
|
||||
>
|
||||
> 模块树应该定义在 *src/lib.rs* 中。这样通过以包名开头的路径,公有项就可以在二进制 crate 中使用。二进制 crate 就变得同其它在该 crate 之外的、使用库 crate 的用户一样:二者都只能使用公有 API。这有助于你设计一个好的 API;你不仅仅是作者,也是用户!
|
||||
>
|
||||
> 在[第十二章][ch12]我们会通过一个同时包含二进制 crate 和库 crate 的命令行程序来展示这些包组织上的实践。
|
||||
> 模块树应该定义在 *src/lib.rs* 中。这样通过以包名开头的路径,公有项就可以在二进制 crate 中使用。二进制 crate 就变得像一个一个完全外部的 crate 来使用库 crate 的用户一样:它只能使用 public API。你不仅仅是作者,也是用户!
|
||||
>
|
||||
> 在[第十二章][ch12]我们会通过一个同时包含二进制 crate 和库 crate 的命令行程序来展示这些组织上的实践。
|
||||
|
||||
### `super` 开始的相对路径
|
||||
|
||||
我们可以通过在路径的开头使用 `super` ,从父模块开始构建相对路径,而不是从当前模块或者 crate 根开始。这类似以 `..` 语法开始一个文件系统路径。使用 `super` 允许我们引用父模块中的已知项,这使得重新组织模块树变得更容易 —— 当模块与父模块关联的很紧密,但某天父模块可能要移动到模块树的其它位置。
|
||||
我们可以通过在路径的开头使用 `super` ,从父模块开始构建相对路径,而不是从当前模块或者 crate 根开始。这类似以 `..` 语法开始一个文件系统路径。使用 `super` 允许我们引用父模块中的已知项,这使得当模块与父模块关联的很紧密,但某天父模块可能要移动到模块树的其它位置时重新组织模块树变得更容。
|
||||
|
||||
考虑一下示例 7-8 中的代码,它模拟了厨师更正了一个错误订单,并亲自将其提供给客户的情况。`back_of_house` 模块中的定义的 `fix_incorrect_order` 函数通过指定的 `super` 起始的 `deliver_order` 路径,来调用父模块中的 `deliver_order` 函数:
|
||||
考虑一下示例 7-8 中的代码,它模拟了厨师更正了一个错误订单并亲自将其提供给客户的情况。`back_of_house` 模块中的定义的 `fix_incorrect_order` 函数通过指定的 `super` 起始的 `deliver_order` 路径来调用父模块中的 `deliver_order` 函数。
|
||||
|
||||
<span class="filename">文件名:src/lib.rs</span>
|
||||
|
||||
@ -111,13 +108,13 @@ Rust 选择以这种方式来实现模块系统功能,因此默认隐藏内部
|
||||
{{#rustdoc_include ../listings/ch07-managing-growing-projects/listing-07-08/src/lib.rs}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 7-8: 使用以 `super` 开头的相对路径从父目录开始调用函数</span>
|
||||
<span class="caption">示例 7-8: 使用以 `super` 开头的相对路径调用函数</span>
|
||||
|
||||
`fix_incorrect_order` 函数在 `back_of_house` 模块中,所以我们可以使用 `super` 进入 `back_of_house` 父模块,也就是本例中的 `crate` 根。在这里,我们可以找到 `deliver_order`。成功!我们认为 `back_of_house` 模块和 `deliver_order` 函数之间可能具有某种关联关系,并且,如果我们要重新组织这个 crate 的模块树,需要一起移动它们。因此,我们使用 `super`,这样一来,如果这些代码被移动到了其他模块,我们只需要更新很少的代码。
|
||||
`fix_incorrect_order` 函数在 `back_of_house` 模块中,所以我们可以使用 `super` 进入 `back_of_house` 父模块,也就是本例中的 `crate` 根。在这里,我们可以找到 `deliver_order`。成功!我们认为 `back_of_house` 模块和 `deliver_order` 函数之间可能保持某种关联关系并且如果我们要重新组织这个 crate 的模块树时,需要一起移动它们。因此,我们使用 `super`,这样一来,如果这些代码被移动到了其他模块,只需要更新很少的代码。
|
||||
|
||||
### 创建公有的结构体和枚举
|
||||
|
||||
我们还可以使用 `pub` 来设计公有的结构体和枚举,不过关于在结构体和枚举上使用 `pub` 还有一些额外的细节需要注意。如果我们在一个结构体定义的前面使用了 `pub` ,这个结构体会变成公有的,但是这个结构体的字段仍然是私有的。我们可以根据情况决定每个字段是否公有。在示例 7-9 中,我们定义了一个公有结构体 `back_of_house::Breakfast`,其中有一个公有字段 `toast` 和私有字段 `seasonal_fruit`。这个例子模拟的情况是,在一家餐馆中,顾客可以选择随餐附赠的面包类型,但是厨师会根据季节和库存情况来决定随餐搭配的水果。餐馆可用的水果变化是很快的,所以顾客不能选择水果,甚至无法看到他们将会得到什么水果。
|
||||
我们还可以使用 `pub` 来设计公有的结构体和枚举,不过关于在结构体和枚举上使用 `pub` 还有一些额外的细节需要注意。如果我们在一个结构体定义的前面使用了 `pub`,这个结构体会变成公有的,但是这个结构体的字段仍然是私有的。我们可以根据情况决定每个字段是否公有。在示例 7-9 中,我们定义了一个公有结构体 `back_of_house::Breakfast`,其中有一个公有字段 `toast` 和私有字段 `seasonal_fruit`。这个例子模拟的情况是,在一家餐馆中,顾客可以选择随餐面包的类型,但是厨师会根据季节和库存情况来决定随餐搭配的水果。餐馆可用的水果变化是很快的,所以顾客不能选择水果,甚至无法看到他们将会得到什么水果。
|
||||
|
||||
<span class="filename">文件名:src/lib.rs</span>
|
||||
|
||||
@ -127,11 +124,11 @@ Rust 选择以这种方式来实现模块系统功能,因此默认隐藏内部
|
||||
|
||||
<span class="caption">示例 7-9: 带有公有和私有字段的结构体</span>
|
||||
|
||||
因为 `back_of_house::Breakfast` 结构体的 `toast` 字段是公有的,所以我们可以在 `eat_at_restaurant` 中使用点号来随意的读写 `toast` 字段。注意,我们不能在 `eat_at_restaurant` 中使用 `seasonal_fruit` 字段,因为 `seasonal_fruit` 是私有的。尝试去除那一行修改 `seasonal_fruit` 字段值的代码的注释,看看你会得到什么错误!
|
||||
因为 `back_of_house::Breakfast` 结构体的 `toast` 字段是公有的,所以我们可以在 `eat_at_restaurant` 中使用点号来读写 `toast` 字段。注意,我们不能在 `eat_at_restaurant` 中使用 `seasonal_fruit` 字段,因为 `seasonal_fruit` 是私有的。尝试去除那一行修改 `seasonal_fruit` 字段值的代码的注释,看看你会得到什么错误!
|
||||
|
||||
还请注意一点,因为 `back_of_house::Breakfast` 具有私有字段,所以这个结构体需要提供一个公共的关联函数来构造 `Breakfast` 的实例 (这里我们命名为 `summer`)。如果 `Breakfast` 没有这样的函数,我们将无法在 `eat_at_restaurant` 中创建 `Breakfast` 实例,因为我们不能在 `eat_at_restaurant` 中设置私有字段 `seasonal_fruit` 的值。
|
||||
|
||||
与之相反,如果我们将枚举设为公有,则它的所有成员都将变为公有。我们只需要在 `enum` 关键字前面加上 `pub`,就像示例 7-10 展示的那样。
|
||||
与之相反,如果我们将枚举设为公有,则它的所有变体都将变为公有。我们只需要在 `enum` 关键字前面加上 `pub`,就像示例 7-10 展示的那样。
|
||||
|
||||
<span class="filename">文件名:src/lib.rs</span>
|
||||
|
||||
@ -141,11 +138,11 @@ Rust 选择以这种方式来实现模块系统功能,因此默认隐藏内部
|
||||
|
||||
<span class="caption">示例 7-10: 设计公有枚举,使其所有成员公有</span>
|
||||
|
||||
因为我们创建了名为 `Appetizer` 的公有枚举,所以我们可以在 `eat_at_restaurant` 中使用 `Soup` 和 `Salad` 成员。
|
||||
因为我们将 `Appetizer` 枚举声明为公有,所以可以在 `eat_at_restaurant` 中使用 `Soup` 和 `Salad` 变体。
|
||||
|
||||
如果枚举成员不是公有的,那么枚举会显得用处不大;给枚举的所有成员挨个添加 `pub` 是很令人恼火的,因此枚举成员默认就是公有的。结构体通常使用时,不必将它们的字段公有化,因此结构体遵循常规,内容全部是私有的,除非使用 `pub` 关键字。
|
||||
如果枚举变体不是公有的,那么枚举会显得用处不大;给枚举的所有变体挨个添加 `pub` 是很令人恼火的,因此枚举变体默认就是公有的。结构体在许多情况下即使字段不可公有也能正常使用,所以结构体字段遵循默认私有的通用规则,除非使用 `pub` 关键字。
|
||||
|
||||
还有一种使用 `pub` 的场景我们还没有涉及到,那就是我们最后要讲的模块功能:`use` 关键字。我们将先单独介绍 `use`,然后展示如何结合使用 `pub` 和 `use`。
|
||||
还有一个我们尚未介绍的与 `pub` 相关的情形,那就是模块系统的最后一个特性:`use` 关键字。我们将先单独介绍 `use`,然后展示如何结合使用 `pub` 和 `use`。
|
||||
|
||||
[pub]: ch07-03-paths-for-referring-to-an-item-in-the-module-tree.html#使用-pub-关键字暴露路径
|
||||
[api-guidelines]: https://rust-lang.github.io/api-guidelines/
|
||||
|
@ -1,10 +1,9 @@
|
||||
## 使用 `use` 关键字将路径引入作用域
|
||||
|
||||
> [ch07-04-bringing-paths-into-scope-with-the-use-keyword.md](https://github.com/rust-lang/book/blob/main/src/ch07-04-bringing-paths-into-scope-with-the-use-keyword.md)
|
||||
> <br>
|
||||
> commit c77d7a1279dbc7a9d76e80c5ac9d742dd529538c
|
||||
<!-- https://github.com/rust-lang/book/blob/main/src/ch07-04-bringing-paths-into-scope-with-the-use-keyword.md -->
|
||||
<!-- commit 72ad14e4acb12438aa467c4cf256e0bc55df585a -->
|
||||
|
||||
不得不编写路径来调用函数显得不便且重复。在示例 7-7 中,无论我们选择 `add_to_waitlist` 函数的绝对路径还是相对路径,每次我们想要调用 `add_to_waitlist` 时,都必须指定`front_of_house` 和 `hosting`。幸运的是,有一种方法可以简化这个过程。我们可以使用 `use` 关键字创建一个短路径,然后就可以在作用域中的任何地方使用这个更短的名字。
|
||||
不得不编写路径来调用函数显得繁琐且重复。在示例 7-7 中,无论我们选择 `add_to_waitlist` 函数的绝对路径还是相对路径,每次我们想要调用 `add_to_waitlist` 时,都必须指定`front_of_house` 和 `hosting`。幸运的是,有一种方法可以简化这个过程。我们可以使用 `use` 关键字创建一个捷径,然后就可以在作用域中的任何地方使用这个更短的名字。
|
||||
|
||||
在示例 7-11 中,我们将 `crate::front_of_house::hosting` 模块引入了 `eat_at_restaurant` 函数的作用域,而我们只需要指定 `hosting::add_to_waitlist` 即可在 `eat_at_restaurant` 中调用 `add_to_waitlist` 函数。
|
||||
|
||||
@ -18,7 +17,7 @@
|
||||
|
||||
在作用域中增加 `use` 和路径类似于在文件系统中创建软连接(符号连接,symbolic link)。通过在 crate 根增加 `use crate::front_of_house::hosting`,现在 `hosting` 在作用域中就是有效的名称了,如同 `hosting` 模块被定义于 crate 根一样。通过 `use` 引入作用域的路径也会检查私有性,同其它路径一样。
|
||||
|
||||
注意 `use` 只能创建 `use` 所在的特定作用域内的短路径。示例 7-12 将 `eat_at_restaurant` 函数移动到了一个叫 `customer` 的子模块,这又是一个不同于 `use` 语句的作用域,所以函数体不能编译。
|
||||
注意 `use` 只能创建 `use` 所在的特定作用域内的捷径。示例 7-12 将 `eat_at_restaurant` 函数移动到了一个叫 `customer` 的子模块,这又是一个不同于 `use` 语句的作用域,所以函数体不能编译。
|
||||
|
||||
<span class="filename">文件名:src/lib.rs</span>
|
||||
|
||||
@ -28,17 +27,17 @@
|
||||
|
||||
<span class="caption">示例 7-12: `use` 语句只适用于其所在的作用域</span>
|
||||
|
||||
编译器错误显示短路径不再适用于 `customer` 模块中:
|
||||
编译器错误显示捷径不再适用于 `customer` 模块中:
|
||||
|
||||
```console
|
||||
{{#include ../listings/ch07-managing-growing-projects/listing-07-12/output.txt}}
|
||||
```
|
||||
|
||||
注意这里还有一个警告说 `use` 在其作用域内不再被使用!为了修复这个问题,可以将 `use` 移动到 `customer` 模块内,或者在子模块 `customer` 内通过 `super::hosting` 引用父模块中的这个短路径。
|
||||
注意这里还有一个警告说 `use` 在其作用域内不再被使用!为了修复这个问题,可以将 `use` 移动到 `customer` 模块内,或者在子模块 `customer` 内通过 `super::hosting` 引用父模块中的这个捷径。
|
||||
|
||||
### 创建惯用的 `use` 路径
|
||||
|
||||
在示例 7-11 中,你可能会比较疑惑,为什么我们是指定 `use crate::front_of_house::hosting` ,然后在 `eat_at_restaurant` 中调用 `hosting::add_to_waitlist` ,而不是通过指定一直到 `add_to_waitlist` 函数的 `use` 路径来得到相同的结果,如示例 7-13 所示。
|
||||
在示例 7-11 中,你可能会比较疑惑,为什么我们是指定 `use crate::front_of_house::hosting`,然后在 `eat_at_restaurant` 中调用 `hosting::add_to_waitlist` ,而不是通过指定一直到 `add_to_waitlist` 函数的 `use` 路径来得到相同的结果,如示例 7-13 所示。
|
||||
|
||||
<span class="filename">文件名:src/lib.rs</span>
|
||||
|
||||
@ -76,7 +75,7 @@
|
||||
|
||||
### 使用 `as` 关键字提供新的名称
|
||||
|
||||
使用 `use` 将两个同名类型引入同一作用域这个问题还有另一个解决办法:在这个类型的路径后面,我们使用 `as` 指定一个新的本地名称或者别名。示例 7-16 展示了另一个编写示例 7-15 中代码的方法,通过 `as` 重命名其中一个 `Result` 类型。
|
||||
使用 `use` 将两个同名类型引入同一作用域这个问题还有另一个解决办法:在这个类型的路径后面,我们使用 `as` 指定一个新的本地名称或者**别名**。示例 7-16 展示了另一个编写示例 7-15 中代码的方法,通过 `as` 重命名其中一个 `Result` 类型。
|
||||
|
||||
<span class="filename">文件名:src/lib.rs</span>
|
||||
|
||||
@ -86,13 +85,13 @@
|
||||
|
||||
<span class="caption">示例 7-16: 使用 `as` 关键字重命名引入作用域的类型</span>
|
||||
|
||||
在第二个 `use` 语句中,我们选择 `IoResult` 作为 `std::io::Result` 的新名称,它与从 `std::fmt` 引入作用域的 `Result` 并不冲突。示例 7-15 和示例 7-16 都是惯用的,如何选择都取决于你!
|
||||
在第二个 `use` 语句中,我们选择 `IoResult` 作为 `std::io::Result` 的新名称,它与从 `std::fmt` 引入作用域的 `Result` 并不冲突。示例 7-15 和示例 7-16 都是惯用写法,如何选择都取决于你!
|
||||
|
||||
### 使用 `pub use` 重导出名称
|
||||
|
||||
使用 `use` 关键字,将某个名称导入当前作用域后,这个名称在此作用域中就可以使用了,但它对此作用域之外还是私有的。如果想让其他人调用我们的代码时,也能够正常使用这个名称,就好像它本来就在当前作用域一样,那我们可以将 `pub` 和 `use` 合起来使用。这种技术被称为 “*重导出*(*re-exporting*)”:我们不仅将一个名称导入了当前作用域,还允许别人把它导入他们自己的作用域。
|
||||
使用 `use` 关键字,将某个名称导入当前作用域后,该名称对此作用域之外还是私有的。若要让作用域之外的代码能够像在当前作用域中一样使用该名称,可以将 `pub` 与 `use` 组合使用。这种技术被称为**重导出**(*re-exporting*),因为在把某个项目导入当前作用域的同时,也将其暴露给其他作用域。
|
||||
|
||||
示例 7-17 将示例 7-11 根模块中的 `use` 改为 `pub use` 。
|
||||
示例 7-17 将示例 7-11 根模块中的 `use` 改为 `pub use` 的代码。
|
||||
|
||||
<span class="filename">文件名:src/lib.rs</span>
|
||||
|
||||
@ -102,13 +101,13 @@
|
||||
|
||||
<span class="caption">示例 7-17: 通过 `pub use` 使名称可从新作用域中被导入至任何代码</span>
|
||||
|
||||
在这个修改之前,外部代码需要使用路径 `restaurant::front_of_house::hosting::add_to_waitlist()` 来调用 `add_to_waitlist` 函数。现在这个 `pub use` 从根模块重导出了 `hosting` 模块,外部代码现在可以使用路径 `restaurant::hosting::add_to_waitlist`。
|
||||
在这个修改之前,外部代码需要使用路径 `restaurant::front_of_house::hosting::add_to_waitlist()` 来调用 `add_to_waitlist` 函数,并且还需要将 `front_of_house` 模块标记为 `pub`。现在这个 `pub use` 从根模块重导出了 `hosting` 模块,外部代码现在可以使用路径 `restaurant::hosting::add_to_waitlist`。
|
||||
|
||||
当你代码的内部结构与调用你代码的程序员所想象的结构不同时,重导出会很有用。例如,在这个餐馆的比喻中,经营餐馆的人会想到“前台”和“后台”。但顾客在光顾一家餐馆时,可能不会以这些术语来考虑餐馆的各个部分。使用 `pub use`,我们可以使用一种结构编写代码,却将不同的结构形式暴露出来。这样做使我们的库井井有条,也使开发这个库的程序员和调用这个库的程序员都更加方便。在[“使用 `pub use` 导出合适的公有 API”][ch14-pub-use]部分让我们再看另一个 `pub use` 的例子来了解这如何影响 crate 的文档。
|
||||
|
||||
### 使用外部包
|
||||
|
||||
在第二章中我们编写了一个猜猜看游戏。那个项目使用了一个外部包,`rand`,来生成随机数。为了在项目中使用 `rand`,在 *Cargo.toml* 中加入了如下行:
|
||||
在第二章中我们编写了一个猜猜看游戏。那个项目使用了一个外部包 `rand` 来生成随机数。为了在项目中使用 `rand`,在 *Cargo.toml* 中加入了如下行:
|
||||
|
||||
<span class="filename">文件名:Cargo.toml</span>
|
||||
|
||||
@ -118,7 +117,7 @@
|
||||
|
||||
在 *Cargo.toml* 中加入 `rand` 依赖告诉了 Cargo 要从 [crates.io](https://crates.io) 下载 `rand` 和其依赖,并使其可在项目代码中使用。
|
||||
|
||||
接着,为了将 `rand` 定义引入项目包的作用域,我们加入一行 `use` 起始的包名,它以 `rand` 包名开头并列出了需要引入作用域的项。回忆一下第二章的 “生成一个随机数” 部分,我们曾将 `Rng` trait 引入作用域并调用了 `rand::thread_rng` 函数:
|
||||
接着,为了将 `rand` 定义引入项目包的作用域,我们加入一行 `use` 起始的包名,它以 `rand` 包名开头并列出了需要引入作用域的项。回忆一下第二章的“生成一个随机数”部分,我们曾将 `Rng` trait 引入作用域并调用了 `rand::thread_rng` 函数:
|
||||
|
||||
```rust,ignore
|
||||
{{#rustdoc_include ../listings/ch02-guessing-game-tutorial/listing-02-03/src/main.rs:ch07-04}}
|
||||
@ -126,7 +125,7 @@
|
||||
|
||||
[crates.io](https://crates.io) 上有很多 Rust 社区成员发布的包,将其引入你自己的项目都需要一道相同的步骤:在 *Cargo.toml* 列出它们并通过 `use` 将其中定义的项引入项目包的作用域中。
|
||||
|
||||
注意 `std` 标准库对于你的包来说也是外部 crate。因为标准库随 Rust 语言一同分发,无需修改 *Cargo.toml* 来引入 `std`,不过需要通过 `use` 将标准库中定义的项引入项目包的作用域中来引用它们,比如我们使用的 `HashMap`:
|
||||
注意 `std` 标准库对于你的包来说也是外部 crate。因为标准库随 Rust 语言一同分发,无需修改 *Cargo.toml* 来引入 `std`,不过需要通过 `use` 将标准库中定义的项引入项目包的作用域中来引用它们。例如,对于 `HashMap`,我们会使用以下语句:
|
||||
|
||||
```rust
|
||||
use std::collections::HashMap;
|
||||
@ -134,9 +133,9 @@ use std::collections::HashMap;
|
||||
|
||||
这是一个以标准库 crate 名 `std` 开头的绝对路径。
|
||||
|
||||
### 嵌套路径来消除大量的 `use` 行
|
||||
### 使用嵌套路径来清理大量的 `use` 列表
|
||||
|
||||
当需要引入很多定义于相同包或相同模块的项时,为每一项单独列出一行会占用源码很大的空间。例如猜猜看章节示例 2-4 中有两行 `use` 语句都从 `std` 引入项到作用域:
|
||||
当需要引入很多定义于相同包或相同模块的项时,为每一项单独列出一行会占用源码大量的垂直空间。例如猜猜看章节示例 2-4 中有两行 `use` 语句都从 `std` 引入项到作用域:
|
||||
|
||||
<span class="filename">文件名:src/main.rs</span>
|
||||
|
||||
@ -178,9 +177,9 @@ use std::collections::HashMap;
|
||||
|
||||
这一行便将 `std::io` 和 `std::io::Write` 同时引入作用域。
|
||||
|
||||
### 通过 glob 运算符将所有的公有定义引入作用域
|
||||
### glob 运算符
|
||||
|
||||
如果希望将一个路径下 **所有** 公有项引入作用域,可以指定路径后跟 `*`,glob 运算符:
|
||||
如果希望将一个路径下**所有**公有项引入作用域,可以指定路径后跟 `*` glob 运算符:
|
||||
|
||||
```rust
|
||||
use std::collections::*;
|
||||
@ -188,7 +187,7 @@ use std::collections::*;
|
||||
|
||||
这个 `use` 语句将 `std::collections` 中定义的所有公有项引入当前作用域。使用 glob 运算符时请多加小心!Glob 会使得我们难以推导作用域中有什么名称和它们是在何处定义的。
|
||||
|
||||
glob 运算符经常用于测试模块 `tests` 中,这时会将所有内容引入作用域;我们将在第十一章 “如何编写测试” 部分讲解。glob 运算符有时也用于 prelude 模式;查看 [标准库中的文档](https://doc.rust-lang.org/std/prelude/index.html#other-preludes) 了解这个模式的更多细节。
|
||||
glob 运算符经常用于测试模块 `tests` 中,这时会将所有内容引入作用域;我们将在第十一章“如何编写测试”部分讲解。glob 运算符有时也用于 prelude 模式;查看[标准库中的文档](https://doc.rust-lang.org/std/prelude/index.html#other-preludes)了解这个模式的更多细节。
|
||||
|
||||
[ch14-pub-use]: ch14-02-publishing-to-crates-io.html#使用-pub-use-导出合适的公有-api
|
||||
[rand]: ch02-00-guessing-game-tutorial.html#生成一个随机数
|
||||
|
@ -1,8 +1,7 @@
|
||||
## 将模块拆分成多个文件
|
||||
|
||||
> [ch07-05-separating-modules-into-different-files.md](https://github.com/rust-lang/book/blob/main/src/ch07-05-separating-modules-into-different-files.md)
|
||||
> <br>
|
||||
> commit 2b4565662d1a7973d870744a923f58f8f7dcce91
|
||||
<!-- https://github.com/rust-lang/book/blob/main/src/ch07-05-separating-modules-into-different-files.md -->
|
||||
<!-- commit 3a30e4c1fbe641afc066b3af9eb01dcdf5ed8b24 -->
|
||||
|
||||
到目前为止,本章所有的例子都在一个文件中定义多个模块。当模块变得更大时,你可能想要将它们的定义移动到单独的文件中,从而使代码更容易阅读。
|
||||
|
||||
@ -26,12 +25,11 @@
|
||||
{{#rustdoc_include ../listings/ch07-managing-growing-projects/listing-07-21-and-22/src/front_of_house.rs}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 7-22: 在 *src/front_of_house.rs* 中定义 `front_of_house`
|
||||
模块</span>
|
||||
<span class="caption">示例 7-22: 在 *src/front_of_house.rs* 中定义 `front_of_house` 模块</span>
|
||||
|
||||
注意你只需在模块树中的某处使用一次 `mod` 声明就可以加载这个文件。一旦编译器知道了这个文件是项目的一部分(并且通过 `mod` 语句的位置知道了代码在模块树中的位置),项目中的其他文件应该使用其所声明的位置的路径来引用那个文件的代码,这在[“引用模块项目的路径”][paths]部分有讲到。换句话说,`mod` **不是** 你可能会在其他编程语言中看到的 "include" 操作。
|
||||
注意你只需在模块树中的某处使用一次 `mod` 声明就可以加载这个文件。一旦编译器知道了这个文件是项目的一部分(并且通过 `mod` 语句的位置知道了代码在模块树中的位置),项目中的其他文件应该使用其所声明的位置的路径来引用那个文件的代码,这在[“引用模块项目的路径”][paths]部分有讲到。换句话说,`mod` **不是**你可能会在其他编程语言中看到的 “include” 操作。
|
||||
|
||||
接下来我们同样将 `hosting` 模块提取到自己的文件中。这个过程会有所不同,因为 `hosting` 是 `front_of_house` 的子模块而不是根模块。我们将 `hosting` 的文件放在与模块树中它的父级模块同名的目录中,在这里是 *src/front_of_house/*。
|
||||
接下来我们同样将 `hosting` 模块提取到自己的文件中。这个过程会有所不同,因为 `hosting` 是 `front_of_house` 的子模块而不是根模块。我们将 `hosting` 的文件放在与模块树中它的父模块同名的目录中,在这里是 *src/front_of_house/*。
|
||||
|
||||
为了移动 `hosting`,修改 *src/front_of_house.rs* 使之仅包含 `hosting` 模块的声明。
|
||||
|
||||
@ -49,11 +47,11 @@
|
||||
{{#rustdoc_include ../listings/ch07-managing-growing-projects/no-listing-02-extracting-hosting/src/front_of_house/hosting.rs}}
|
||||
```
|
||||
|
||||
如果将 *hosting.rs* 放在 *src* 目录,编译器会认为 `hosting` 模块中的 *hosting.rs* 的代码声明于 crate 根,而不是声明为 `front_of_house` 的子模块。编译器所遵循的哪些文件对应哪些模块的代码的规则,意味着目录和文件更接近于模块树。
|
||||
如果将 *hosting.rs* 放在 *src* 目录,编译器会认为 `hosting` 模块中的 *hosting.rs* 的代码声明于 crate 根,而不是声明为 `front_of_house` 的子模块。编译器所遵循的哪些文件对应哪些模块的代码的规则,意味着目录和文件更紧密地贴合模块树。
|
||||
|
||||
> ### 另一种文件路径
|
||||
>
|
||||
> 目前为止我们介绍了 Rust 编译器所最常用的文件路径;不过一种更老的文件路径也仍然是支持的。
|
||||
> 目前为止我们介绍了 Rust 编译器所最常用的文件路径,但 Rust 也支持一种更老的路径风格。
|
||||
>
|
||||
> 对于声明于 crate 根的 `front_of_house` 模块,编译器会在如下位置查找模块代码:
|
||||
>
|
||||
@ -69,14 +67,14 @@
|
||||
>
|
||||
> 使用 *mod.rs* 这一文件名的风格的主要缺点是会导致项目中出现很多 *mod.rs* 文件,当你在编辑器中同时打开它们时会感到疑惑。
|
||||
|
||||
我们将各个模块的代码移动到独立文件了,同时模块树依旧相同。`eat_at_restaurant` 中的函数调用也无需修改继续保持有效,即便其定义存在于不同的文件中。这个技巧让你可以在模块代码增长时,将它们移动到新文件中。
|
||||
我们将各个模块的代码移动到独立文件了,同时模块树保持不变。`eat_at_restaurant` 中的函数调用也无需修改继续保持有效,即便其定义存在于不同的文件中。这个技巧让你可以在模块代码增长时,将它们移动到新文件中。
|
||||
|
||||
注意,*src/lib.rs* 中的 `pub use crate::front_of_house::hosting` 语句也并未发生改变。use 也不会对哪些文件会被编译为 crate 的一部分有任何影响。`mod` 关键字声明了模块,而 Rust 会在与模块同名的文件中查找模块的代码。
|
||||
注意,*src/lib.rs* 中的 `pub use crate::front_of_house::hosting` 语句也并未发生改变,`use` 也不会对哪些文件会被编译为 crate 的一部分有任何影响。`mod` 关键字声明了模块,而 Rust 会在与模块同名的文件中查找模块的代码。
|
||||
|
||||
|
||||
## 总结
|
||||
|
||||
Rust 提供了将包分成多个 crate,将 crate 分成模块,以及通过指定绝对或相对路径从一个模块引用另一个模块中定义的项的方式。你可以通过使用 `use` 语句将路径引入作用域,这样在多次使用时可以使用更短的路径。模块定义的代码默认是私有的,不过可以选择增加 `pub` 关键字使其定义变为公有。
|
||||
Rust 允许你将一个包拆分为多个 crate,并将一个 crate 拆分为若干模块,从而可以在一个模块中引用另一个模块中定义的项。你可以使用绝对路径或相对路径来实现这一点。你可以通过使用 `use` 语句将路径引入作用域,这样在多次使用时可以使用更短的路径。模块定义的代码默认是私有的,不过可以选择增加 `pub` 关键字使其定义变为公有。
|
||||
|
||||
接下来,让我们看看一些标准库提供的集合数据类型,你可以利用它们编写出漂亮整洁的代码。
|
||||
|
||||
|
@ -1,14 +1,13 @@
|
||||
# 常见集合
|
||||
|
||||
> [ch08-00-common-collections.md](https://github.com/rust-lang/book/blob/main/src/ch08-00-common-collections.md)
|
||||
> <br>
|
||||
> commit 1fd890031311612e54965f7f800a8c8bd4464663
|
||||
<!-- https://github.com/rust-lang/book/blob/main/src/ch08-00-common-collections.md -->
|
||||
<!-- commit 5d22a358fb2380aa3f270d7b6269b67b8e44849e -->
|
||||
|
||||
Rust 标准库中包含一系列被称为 **集合**(*collections*)的非常有用的数据结构。大部分其他数据类型都代表一个特定的值,不过集合可以包含多个值。不同于内建的数组和元组类型,这些集合指向的数据是储存在堆上的,这意味着数据的数量不必在编译时就已知,并且还可以随着程序的运行增长或缩小。每种集合都有着不同功能和成本,而根据当前情况选择合适的集合,这是一项应当逐渐掌握的技能。在这一章里,我们将详细的了解三个在 Rust 程序中被广泛使用的集合:
|
||||
Rust 标准库中包含一系列被称为 **集合**(*collections*)的非常有用的数据结构。大部分其他数据类型都代表一个特定的值,不过集合可以包含多个值。不同于内建的数组和元组类型,这些集合指向的数据是储存在堆上的,这意味着数据的数量不必在编译时就已知,并且还可以随着程序的运行增长或缩小。每种集合都有着不同功能和开销,而根据当前情况选择合适的集合,这是一项需要逐渐掌握的技能。在这一章里,我们将详细的了解三个在 Rust 程序中被广泛使用的集合:
|
||||
|
||||
* *vector* 允许我们一个挨着一个地储存一系列数量可变的值
|
||||
* **字符串**(*string*)是字符的集合。我们之前见过 `String` 类型,不过在本章我们将深入了解。
|
||||
* **哈希 map**(*hash map*)允许我们将值与一个特定的键(key)相关联。这是一个叫做 *map* 的更通用的数据结构的特定实现。
|
||||
- **向量**(*vector*)允许我们一个挨着一个地储存一系列数量可变的值。
|
||||
- **字符串**(*string*)是字符的集合。我们之前见过 `String` 类型,不过在本章我们将深入了解。
|
||||
- **哈希 map**(*hash map*)允许我们将值与一个特定的键(key)相关联。这是一个叫做 *map* 的更通用的数据结构的特定实现。
|
||||
|
||||
对于标准库提供的其他类型的集合,请查看[文档][collections]。
|
||||
|
||||
|
@ -1,20 +1,19 @@
|
||||
## 使用 Vector 储存列表
|
||||
|
||||
> [ch08-01-vectors.md](https://github.com/rust-lang/book/blob/main/src/ch08-01-vectors.md)
|
||||
> <br>
|
||||
> commit ac16184a7f56d17daa9c4c76901371085dc0ac43
|
||||
<!-- https://github.com/rust-lang/book/blob/main/src/ch08-01-vectors.md -->
|
||||
<!-- commit 5d22a358fb2380aa3f270d7b6269b67b8e44849e -->
|
||||
|
||||
我们要讲到的第一个类型是 `Vec<T>`,也被称为 _vector_。vector 允许我们在一个单独的数据结构中储存多于一个的值,它在内存中彼此相邻地排列所有的值。vector 只能储存相同类型的值。它们在拥有一系列项的场景下非常实用,例如文件中的文本行或是购物车中商品的价格。
|
||||
我们要讲到的第一个类型是 `Vec<T>`,也被称为 *vector*。vector 允许我们在一个单独的数据结构中储存多于一个的值,它在内存中彼此相邻地排列所有的值。vector 只能储存相同类型的值。它们在拥有一系列项的场景下非常实用,例如文件中的文本行或是购物车中商品的价格。
|
||||
|
||||
### 新建 vector
|
||||
|
||||
为了创建一个新的空 vector,可以调用 `Vec::new` 函数,如示例 8-1 所示:
|
||||
为了新建一个的空 vector,可以调用 `Vec::new` 函数,如示例 8-1 所示。
|
||||
|
||||
```rust
|
||||
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-01/src/main.rs:here}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 8-1:新建一个空的 vector 来储存 `i32` 类型的值</span>
|
||||
<span class="caption">示例 8-1:新建一个的空 vector 来储存 `i32` 类型的值</span>
|
||||
|
||||
注意这里我们增加了一个类型注解。因为没有向这个 vector 中插入任何值,Rust 并不知道我们想要储存什么类型的元素。这是一个非常重要的点。vector 是用泛型实现的,第十章会涉及到如何对你自己的类型使用它们。现在,所有你需要知道的就是 `Vec<T>` 是一个由标准库提供的类型,它可以存放任何类型,而当 `Vec` 存放某个特定类型时,那个类型位于尖括号中。在示例 8-1 中,我们告诉 Rust `v` 这个 `Vec<T>` 将存放 `i32` 类型的元素。
|
||||
|
||||
@ -62,17 +61,17 @@ Rust 提供了两种引用元素的方法的原因是当尝试使用现有元素
|
||||
|
||||
<span class="caption">示例 8-5:尝试访问一个包含 5 个元素的 vector 的索引 100 处的元素</span>
|
||||
|
||||
当运行这段代码,你会发现对于第一个 `[]` 方法,当引用一个不存在的元素时 Rust 会造成 panic。这个方法更适合当程序认为尝试访问超过 vector 结尾的元素是一个严重错误的情况,这时应该使程序崩溃。
|
||||
当运行这段代码,你会发现对于第一个 `[]` 方法,当引用一个不存在的元素时 Rust 会造成 panic。此方法适用于当你希望在尝试访问 vector 末尾之外的元素时让程序直接崩溃的场景。
|
||||
|
||||
当 `get` 方法被传递了一个数组外的索引时,它不会 panic 而是返回 `None`。当偶尔出现超过 vector 范围的访问属于正常情况的时候可以考虑使用它。接着你的代码可以有处理 `Some(&element)` 或 `None` 的逻辑,如第六章讨论的那样。例如,索引可能来源于用户输入的数字。如果它们不慎输入了一个过大的数字那么程序就会得到 `None` 值,你可以告诉用户当前 vector 元素的数量并再请求它们输入一个有效的值。这就比因为输入错误而使程序崩溃要友好的多!
|
||||
|
||||
一旦程序获取了一个有效的引用,借用检查器将会执行所有权和借用规则(第四章讲到)来确保 vector 内容的这个引用和任何其他引用保持有效。回忆一下不能在相同作用域中同时存在可变和不可变引用的规则。这个规则适用于示例 8-6,当我们获取了 vector 的第一个元素的不可变引用并尝试在 vector 末尾增加一个元素的时候,如果尝试在函数的后面引用这个元素是行不通的:
|
||||
一旦程序获取了一个有效的引用,借用检查器将会执行所有权和借用规则(第四章讲到)来确保 vector 内容的这个引用和任何其他引用保持有效。回忆一下不能在相同作用域中同时存在可变和不可变引用的规则。这个规则适用于示例 8-6,当我们获取了 vector 的第一个元素的不可变引用并尝试在 vector 末尾增加一个元素的时候,如果尝试在函数的后面再次引用这个元素是行不通的:
|
||||
|
||||
```rust,ignore,does_not_compile
|
||||
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-06/src/main.rs:here}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 8-6:在拥有 vector 中项的引用的同时向其增加一个元素</span>
|
||||
<span class="caption">示例 8-6:尝试在拥有 vector 中项的引用的同时向其增加一个元素</span>
|
||||
|
||||
编译会给出这个错误:
|
||||
|
||||
@ -102,15 +101,15 @@ Rust 提供了两种引用元素的方法的原因是当尝试使用现有元素
|
||||
|
||||
<span class="caption">示例 8-8:遍历 vector 中元素的可变引用</span>
|
||||
|
||||
为了修改可变引用所指向的值,在使用 `+=` 运算符之前必须使用解引用运算符(`*`)获取 `i` 中的值。第十五章的 [“通过解引用运算符追踪指针的值”][deref] 部分会详细介绍解引用运算符。
|
||||
为了修改可变引用所指向的值,在使用 `+=` 运算符之前必须使用解引用运算符(`*`)获取 `i` 中的值。第十五章的[“通过解引用运算符追踪指针的值”][deref]部分会详细介绍解引用运算符。
|
||||
|
||||
因为借用检查器的规则,无论可变还是不可变地遍历一个 vector 都是安全的。如果尝试在示例 8-7 和 示例 8-8 的 `for` 循环体内插入或删除项,都会得到一个类似示例 8-6 代码中类似的编译错误。`for` 循环中获取的 vector 引用阻止了同时对 vector 整体的修改。
|
||||
由于借用检查器的规则,无论可变还是不可变地遍历一个 vector 都是安全的。如果尝试在示例 8-7 和 示例 8-8 的 `for` 循环体内插入或删除项,都会得到一个类似示例 8-6 代码中类似的编译错误。`for` 循环中获取的 vector 引用阻止了同时对整个 vector 进行修改。
|
||||
|
||||
### 使用枚举来储存多种类型
|
||||
|
||||
vector 只能储存相同类型的值。这是很不方便的;绝对会有需要储存一系列不同类型的值的用例。幸运的是,枚举的成员都被定义为相同的枚举类型,所以当需要在 vector 中储存不同类型值时,我们可以定义并使用一个枚举!
|
||||
|
||||
例如,假如我们想要从电子表格的一行中获取值,而这一行的有些列包含数字,有些包含浮点值,还有些是字符串。我们可以定义一个枚举,其成员会存放这些不同类型的值,同时所有这些枚举成员都会被当作相同类型:那个枚举的类型。接着可以创建一个储存枚举值的 vector,这样最终就能够储存不同类型的值了。示例 8-9 展示了其用例:
|
||||
例如,假如我们想要从电子表格的一行中获取值,而这一行的有些列包含数字,有些包含浮点值,还有些是字符串。我们可以定义一个枚举,其成员会存放这些不同类型的值,同时所有这些枚举成员都会被当作相同类型:那个枚举的类型。接着可以创建一个储存该枚举值的 vector,这样最终就能够储存不同类型的值了。示例 8-9 展示了这个用法:
|
||||
|
||||
```rust
|
||||
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-09/src/main.rs:here}}
|
||||
@ -126,7 +125,7 @@ Rust 在编译时必须确切知道 vector 中的类型,这样它才能确定
|
||||
|
||||
### 丢弃 vector 时也会丢弃其所有元素
|
||||
|
||||
类似于任何其他的 `struct`,vector 在其离开作用域时会被释放,如示例 8-4 所标注的:
|
||||
类似于任何其他的 `struct`,vector 在其离开作用域时会被释放,如示例 8-10 所标注的:
|
||||
|
||||
```rust
|
||||
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-10/src/main.rs:here}}
|
||||
|
@ -1,22 +1,21 @@
|
||||
## 使用字符串储存 UTF-8 编码的文本
|
||||
|
||||
> [ch08-02-strings.md](https://github.com/rust-lang/book/blob/main/src/ch08-02-strings.md)
|
||||
> <br>
|
||||
> commit 668c64760b5c7ea654facb4ba5fe9faddfda27cc
|
||||
<!-- https://github.com/rust-lang/book/blob/main/src/ch08-02-strings.md -->
|
||||
<!-- commit 3a30e4c1fbe641afc066b3af9eb01dcdf5ed8b24 -->
|
||||
|
||||
第四章已经讲过一些字符串的内容,不过现在让我们更深入地了解它。字符串是新晋 Rustacean 们通常会被困住的领域,这是由于三方面理由的结合:Rust 倾向于确保暴露出可能的错误,字符串是比很多程序员所想象的要更为复杂的数据结构,以及 UTF-8。所有这些要素结合起来对于来自其他语言背景的程序员就可能显得很困难了。
|
||||
|
||||
在集合章节中讨论字符串的原因是,字符串就是作为字节的集合外加一些方法实现的,当这些字节被解释为文本时,这些方法提供了实用的功能。在这一部分,我们会讲到 `String` 中那些任何集合类型都有的操作,比如创建、更新和读取。也会讨论 `String` 与其他集合不一样的地方,例如索引 `String` 是很复杂的,由于人和计算机理解 `String` 数据方式的不同。
|
||||
在集合章节中讨论字符串的原因是,字符串就是作为字节的集合外加一些方法实现的,当这些字节被解释为文本时,这些方法提供了实用的功能。在本小节中,我们会讲到 `String` 中那些任何集合类型都有的操作,比如创建、更新和读取。也会讨论 `String` 与其他集合不一样的地方,例如索引 `String` 是很复杂的,由于人和计算机理解 `String` 数据方式的不同。
|
||||
|
||||
### 什么是字符串?
|
||||
|
||||
在开始深入这些方面之前,我们需要讨论一下术语 **字符串** 的具体意义。Rust 的核心语言中只有一种字符串类型:字符串 slice `str`,它通常以被借用的形式出现,`&str`。第四章讲到了 **字符串 slices**:它们是一些对储存在别处的 UTF-8 编码字符串数据的引用。举例来说,由于字符串字面值被储存在程序的二进制输出中,因此字符串字面值也是字符串 slices。
|
||||
我们先定义一下**字符串**这一术语的具体意义。Rust 的核心语言中只有一种字符串类型,字符串 slice `str`,它通常以被借用的形式出现,`&str`。第四章讲到了**字符串 slices**:它们是一些对储存在别处的 UTF-8 编码字符串数据的引用。举例来说,由于字符串字面值被储存在程序的二进制输出中,因此它们也是字符串 slices。
|
||||
|
||||
字符串(`String`)类型由 Rust 标准库提供,而不是编入核心语言,它是一种可增长、可变、可拥有、UTF-8 编码的字符串类型。当 Rustaceans 提及 Rust 中的 "字符串 "时,他们可能指的是 `String` 或 string slice `&str` 类型,而不仅仅是其中一种类型。虽然本节主要讨论 `String`,但这两种类型在 Rust 的标准库中都有大量使用,而且 `String` 和 字符串 slices 都是 UTF-8 编码的。
|
||||
|
||||
### 新建字符串
|
||||
|
||||
很多 `Vec` 可用的操作在 `String` 中同样可用,事实上 `String` 被实现为一个带有一些额外保证、限制和功能的字节 vector 的封装。其中一个同样作用于 `Vec<T>` 和 `String` 函数的例子是用来新建一个实例的 `new` 函数,如示例 8-11 所示。
|
||||
很多 `Vec<T>` 上可用的操作在 `String` 中同样可用,事实上 `String` 被实现为一个带有一些额外保证、限制和功能的字节 vector 的封装。其中一个同样作用于 `Vec<T>` 和 `String` 函数的例子是用来新建一个实例的 `new` 函数,如示例 8-11 所示。
|
||||
|
||||
```rust
|
||||
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-11/src/main.rs:here}}
|
||||
@ -24,7 +23,7 @@
|
||||
|
||||
<span class="caption">示例 8-11:新建一个空的 `String`</span>
|
||||
|
||||
这新建了一个叫做 `s` 的空的字符串,接着我们可以向其中装载数据。通常字符串会有初始数据,因为我们希望一开始就有这个字符串。为此,可以使用 `to_string` 方法,它能用于任何实现了 `Display` trait 的类型,比如字符串字面值。示例 8-12 展示了两个例子。
|
||||
这新建了一个叫做 `s` 的空的字符串,接着我们可以向其中加载数据。通常字符串会有初始数据,因为我们希望一开始就有这个字符串。为此,可以使用 `to_string` 方法,它能用于任何实现了 `Display` trait 的类型,比如字符串字面值。示例 8-12 展示了两个例子。
|
||||
|
||||
```rust
|
||||
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-12/src/main.rs:here}}
|
||||
@ -44,7 +43,7 @@
|
||||
|
||||
因为字符串应用广泛,这里有很多不同的用于字符串的通用 API 可供选择。其中一些可能看起来多余,不过都有其用武之地!在这个例子中,`String::from` 和 `.to_string` 最终做了完全相同的工作,所以如何选择就是代码风格与可读性的问题了。
|
||||
|
||||
记住字符串是 UTF-8 编码的,所以可以包含任何可以正确编码的数据,如示例 8-14 所示。
|
||||
记住字符串是 UTF-8 编码的,所以可以包含任何经过正确编码的数据,如示例 8-14 所示。
|
||||
|
||||
```rust
|
||||
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-14/src/main.rs:here}}
|
||||
@ -78,7 +77,7 @@
|
||||
|
||||
如果 `push_str` 方法获取了 `s2` 的所有权,就不能在最后一行打印出其值了。好在代码如我们期望那样工作!
|
||||
|
||||
`push` 方法被定义为获取一个单独的字符作为参数,并附加到 `String` 中。示例 8-17 展示了使用 `push` 方法将字母 "l" 加入 `String` 的代码。
|
||||
`push` 方法被定义为获取一个单独的字符作为参数,并附加到 `String` 中。示例 8-17 展示了使用 `push` 方法将字母 *l* 加入 `String` 的代码。
|
||||
|
||||
```rust
|
||||
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-17/src/main.rs:here}}
|
||||
@ -86,7 +85,7 @@
|
||||
|
||||
<span class="caption">示例 8-17:使用 `push` 将一个字符加入 `String` 值中</span>
|
||||
|
||||
执行这些代码之后,`s` 将会包含 “lol”。
|
||||
执行这些代码之后,`s` 将会包含 `lol`。
|
||||
|
||||
#### 使用 `+` 运算符或 `format!` 宏拼接字符串
|
||||
|
||||
@ -106,25 +105,25 @@ fn add(self, s: &str) -> String {
|
||||
|
||||
在标准库中你会发现,`add` 的定义使用了泛型和关联类型。在这里我们替换为了具体类型,这也正是当使用 `String` 值调用这个方法会发生的。第十章会讨论泛型。这个签名提供了理解 `+` 运算那微妙部分的线索。
|
||||
|
||||
首先,`s2` 使用了 `&`,意味着我们使用第二个字符串的 **引用** 与第一个字符串相加。这是因为 `add` 函数的 `s` 参数:只能将 `&str` 和 `String` 相加,不能将两个 `String` 值相加。不过等一下 —— `&s2` 的类型是 `&String`, 而不是 `add` 第二个参数所指定的 `&str`。那么为什么示例 8-18 还能编译呢?
|
||||
首先,`s2` 使用了 `&`,意味着我们使用第二个字符串的**引用**与第一个字符串相加。这是因为 `add` 函数的 `s` 参数:只能将 `&str` 和 `String` 相加,不能将两个 `String` 值相加。不过等一下 —— `&s2` 的类型是 `&String`, 而不是 `add` 第二个参数所指定的 `&str`。那么为什么示例 8-18 还能编译呢?
|
||||
|
||||
之所以能够在 `add` 调用中使用 `&s2` 是因为 `&String` 可以被 **强转**(*coerced*)成 `&str`。当`add`函数被调用时,Rust 使用了一个被称为 **Deref 强制转换**(*deref coercion*)的技术,你可以将其理解为它把 `&s2` 变成了 `&s2[..]`。第十五章会更深入的讨论 Deref 强制转换。因为 `add` 没有获取参数的所有权,所以 `s2` 在这个操作后仍然是有效的 `String`。
|
||||
之所以能够在 `add` 调用中使用 `&s2` 是因为 `&String` 可以被 **强转**(*coerced*)成 `&str`。当`add`函数被调用时,Rust 使用了一个被称为 **Deref 强制转换**(*deref coercion*)的技术,实际上会把 `&s2` 转换为 `&s2[..]`。第十五章会更深入的讨论 Deref 强制转换。因为 `add` 没有获取参数的所有权,所以在这个操作后 `s2` 仍然是有效的 `String`。
|
||||
|
||||
其次,可以发现签名中 `add` 获取了 `self` 的所有权,因为 `self` **没有** 使用 `&`。这意味着示例 8-18 中的 `s1` 的所有权将被移动到 `add` 调用中,之后就不再有效。所以虽然 `let s3 = s1 + &s2;` 看起来就像它会复制两个字符串并创建一个新的字符串,而实际上这个语句会获取 `s1` 的所有权,附加上从 `s2` 中拷贝的内容,并返回结果的所有权。换句话说,它看起来好像生成了很多拷贝,不过实际上并没有:这个实现比拷贝要更高效。
|
||||
其次,可以发现签名中 `add` 获取了 `self` 的所有权,因为 `self` **没有**使用 `&`。这意味着示例 8-18 中的 `s1` 的所有权将被移动到 `add` 调用中,之后就不再有效。所以虽然 `let s3 = s1 + &s2;` 看起来就像它会复制两个字符串并创建一个新的字符串,而实际上这个语句会获取 `s1` 的所有权,附加上从 `s2` 中拷贝的内容,并返回结果的所有权。换句话说,它看起来好像生成了很多拷贝,不过实际上并没有:这个实现比拷贝要更高效。
|
||||
|
||||
如果想要级联多个字符串,`+` 的行为就显得笨重了:
|
||||
如果想要级联多个字符串,`+` 运算符的行为就显得笨重了:
|
||||
|
||||
```rust
|
||||
{{#rustdoc_include ../listings/ch08-common-collections/no-listing-01-concat-multiple-strings/src/main.rs:here}}
|
||||
```
|
||||
|
||||
这时 `s` 的内容会是 “tic-tac-toe”。在有这么多 `+` 和 `"` 字符的情况下,很难理解具体发生了什么。对于更为复杂的字符串链接,可以使用 `format!` 宏:
|
||||
这时 `s` 的内容会是 `tic-tac-toe`。在有这么多 `+` 和 `"` 字符的情况下,很难理解具体发生了什么。对于更为复杂的字符串链接,可以使用 `format!` 宏:
|
||||
|
||||
```rust
|
||||
{{#rustdoc_include ../listings/ch08-common-collections/no-listing-02-format/src/main.rs:here}}
|
||||
```
|
||||
|
||||
这些代码也会将 `s` 设置为 “tic-tac-toe”。`format!` 与 `println!` 的工作原理相同,不过不同于将输出打印到屏幕上,它返回一个带有结果内容的 `String`。这个版本就好理解的多,宏 `format!` 生成的代码使用引用所以不会获取任何参数的所有权。
|
||||
这些代码也会将 `s` 设置为 `tic-tac-toe`。`format!` 与 `println!` 的工作原理相同,不过不同于将输出打印到屏幕上,它返回一个带有结果内容的 `String`。这个版本就好理解的多,宏 `format!` 生成的代码使用引用因此不会获取任何参数的所有权。
|
||||
|
||||
### 索引字符串
|
||||
|
||||
@ -142,36 +141,36 @@ fn add(self, s: &str) -> String {
|
||||
{{#include ../listings/ch08-common-collections/listing-08-19/output.txt}}
|
||||
```
|
||||
|
||||
错误和提示说明了全部问题:Rust 的字符串不支持索引。那么接下来的问题是,为什么不支持呢?为了回答这个问题,我们必须先聊一聊 Rust 是如何在内存中储存字符串的。
|
||||
错误和提示说明了全部问题:Rust 的字符串不支持索引。那么,为什么会这样呢?为了回答这个问题,我们必须先聊一聊 Rust 是如何在内存中储存字符串的。
|
||||
|
||||
#### 内部表现
|
||||
|
||||
`String` 是一个 `Vec<u8>` 的封装。让我们看看示例 8-14 中一些正确编码的字符串的例子。首先是这一个:
|
||||
`String` 是一个 `Vec<u8>` 的封装。让我们看看示例 8-14 中一些正确编码的字符串的例子。首先是这一例:
|
||||
|
||||
```rust
|
||||
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-14/src/main.rs:spanish}}
|
||||
```
|
||||
|
||||
在这里,`len` 的值是 4,这意味着储存字符串 “Hola” 的 `Vec` 的长度是四个字节:这里每一个字母的 UTF-8 编码都占用一个字节。那下面这个例子又如何呢?(注意这个字符串中的首字母是西里尔字母的 Ze 而不是数字 3。)
|
||||
在这里,`len` 的值是 `4`,这意味着储存字符串 `"Hola"` 的 vector 的长度是四个字节:这里每一个字母的 UTF-8 编码都占用一个字节。下面这一行可能会让你感到意外(注意这个字符串中的首字母是西里尔字母的 *Ze* 而不是数字 3。):
|
||||
|
||||
```rust
|
||||
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-14/src/main.rs:russian}}
|
||||
```
|
||||
|
||||
当问及这个字符是多长的时候有人可能会说是 12。然而,Rust 的回答是 24。这是使用 UTF-8 编码 “Здравствуйте” 所需要的字节数,这是因为每个 Unicode 标量值需要两个字节存储。因此一个字符串字节值的索引并不总是对应一个有效的 Unicode 标量值。作为演示,考虑如下无效的 Rust 代码:
|
||||
如果有人问及该字符串的长度,你可能会回答 12。然而,Rust 的回答是 24:这是使用 UTF-8 编码 “Здравствуйте” 所需要的字节数,这是因为在这个字符串中每个 Unicode 标量值需要两个字节存储。因此一个字符串字节值的索引并不总是对应一个有效的 Unicode 标量值。作为演示,考虑如下无效的 Rust 代码:
|
||||
|
||||
```rust,ignore,does_not_compile
|
||||
let hello = "Здравствуйте";
|
||||
let answer = &hello[0];
|
||||
```
|
||||
|
||||
我们已经知道 `answer` 不是第一个字符 `3`。当使用 UTF-8 编码时,(西里尔字母的 Ze)`З` 的第一个字节是 `208`,第二个是 `151`,所以 `answer` 实际上应该是 `208`,不过 `208` 自身并不是一个有效的字母。返回 `208` 可不是一个请求字符串第一个字母的人所希望看到的,不过它是 Rust 在字节索引 0 位置所能提供的唯一数据。用户通常不会想要一个字节值被返回。即使这个字符串只有拉丁字母,如果 `&"hello"[0]` 是返回字节值的有效代码,它也会返回 `104` 而不是 `h`。
|
||||
我们已经知道 `answer` 不是第一个字符 `З`。当使用 UTF-8 编码时,`З` 的第一个字节是 `208`,第二个是 `151`,所以 `answer` 实际上应该是 `208`,不过 `208` 自身并不是一个有效的字母。返回 `208` 可不是一个请求字符串第一个字母的人所希望看到的,不过它是 Rust 在字节索引 0 位置所能提供的唯一数据。用户通常不会想要一个字节值被返回,即使这个字符串只有拉丁字母,如果 `&"hi"[0]` 是返回字节值的有效代码,它也会返回 `104` 而不是 `h`。
|
||||
|
||||
为了避免返回意外的值并造成不能立刻发现的 bug,Rust 根本不会编译这些代码,并在开发过程中及早杜绝了误会的发生。
|
||||
|
||||
#### 字节、标量值和字形簇!天呐!
|
||||
|
||||
这引起了关于 UTF-8 的另外一个问题:从 Rust 的角度来讲,事实上有三种相关方式可以理解字符串:字节、标量值和字形簇(最接近人们眼中 **字母** 的概念)。
|
||||
这引起了关于 UTF-8 的另外一个问题:从 Rust 的角度来讲,事实上有三种相关方式可以查看字符串:字节、标量值和字形簇(最接近人们眼中 **字母**(*letters*)的概念)。
|
||||
|
||||
比如这个用梵文书写的印度语单词 “नमस्ते”,最终它储存在 vector 中的 `u8` 值看起来像这样:
|
||||
|
||||
@ -206,15 +205,15 @@ let hello = "Здравствуйте";
|
||||
let s = &hello[0..4];
|
||||
```
|
||||
|
||||
这里,`s` 会是一个 `&str`,它包含字符串的头四个字节。早些时候,我们提到了这些字母都是两个字节长的,所以这意味着 `s` 将会是 “Зд”。
|
||||
这里,`s` 会是一个 `&str`,它包含字符串的头四个字节。早些时候,我们提到了这些字母都是两个字节长的,所以这意味着 `s` 将会是 `Зд`。
|
||||
|
||||
如果获取 `&hello[0..1]` 会发生什么呢?答案是:Rust 在运行时会 panic,就跟访问 vector 中的无效索引时一样:
|
||||
如果尝试用类似 `&hello[0..1]` 的方式对字符的部分字节进行 slice,Rust 会在运行时 panic,就跟访问 vector 中的无效索引时一样:
|
||||
|
||||
```console
|
||||
{{#include ../listings/ch08-common-collections/output-only-01-not-char-boundary/output.txt}}
|
||||
```
|
||||
|
||||
你应该小心谨慎地使用这个操作,因为这么做可能会使你的程序崩溃。
|
||||
在使用 range 来创建字符串 slice 时要格外小心,因为这么做可能会使你的程序崩溃。
|
||||
|
||||
### 遍历字符串的方法
|
||||
|
||||
@ -241,7 +240,7 @@ for b in "Зд".bytes() {
|
||||
}
|
||||
```
|
||||
|
||||
这些代码会打印出组成 `String` 的 4 个字节:
|
||||
这些代码会打印出组成字符串的四个字节:
|
||||
|
||||
```text
|
||||
208
|
||||
@ -250,17 +249,14 @@ for b in "Зд".bytes() {
|
||||
180
|
||||
```
|
||||
|
||||
不过请记住有效的 Unicode 标量值可能会由不止一个字节组成。
|
||||
不过请务必记住有效的 Unicode 标量值可能会由不止一个字节组成。
|
||||
|
||||
从字符串中获取如同天城文这样的字形簇是很复杂的,所以标准库并没有提供这个功能。[crates.io](https://crates.io/)<!-- ignore --> 上有些提供这样功能的 crate。
|
||||
|
||||
### 字符串并不简单
|
||||
|
||||
总而言之,字符串还是很复杂的。不同的语言选择了不同的向程序员展示其复杂性的方式。Rust 选择了以准确的方式处理 `String` 数据作为所有 Rust 程序的默认行为,这意味着程序员们必须更多的思考如何预先处理 UTF-8 数据。这种权衡取舍相比其他语言更多的暴露出了字符串的复杂性,不过也使你在开发周期后期免于处理涉及非 ASCII 字符的错误。
|
||||
总而言之,字符串还是很复杂的。不同的语言选择了不同的向程序员展示其复杂性的方式。Rust 选择了以准确的方式处理 `String` 数据作为所有 Rust 程序的默认行为,这意味着程序员们必须更多的思考如何预先处理 UTF-8 数据。这种权衡相比其他语言更多地暴露出了字符串的复杂性,不过也使你在开发周期后期免于处理涉及非 ASCII 字符的错误。
|
||||
|
||||
好消息是标准库提供了很多围绕 `String` 和 `&str` 构建的功能,来帮助我们正确处理这些复杂场景。请务必查看这些使用方法的文档,例如 `contains` 来搜索一个字符串,和 `replace` 将字符串的一部分替换为另一个字符串。
|
||||
|
||||
|
||||
称作 `String` 的类型是由标准库提供的,而没有写进核心语言部分,它是可增长的、可变的、有所有权的、UTF-8 编码的字符串类型。当 Rustacean 们谈到 Rust 的 “字符串”时,它们通常指的是 `String` 或字符串 slice `&str` 类型,而不特指其中某一个。虽然本部分内容大多是关于 `String` 的,不过这两个类型在 Rust 标准库中都被广泛使用,`String` 和字符串 slices 都是 UTF-8 编码的。
|
||||
|
||||
现在让我们转向一些不太复杂的集合:哈希 map!
|
||||
|
@ -1,18 +1,17 @@
|
||||
## 使用 Hash Map 储存键值对
|
||||
|
||||
> [ch08-03-hash-maps.md](https://github.com/rust-lang/book/blob/main/src/ch08-03-hash-maps.md)
|
||||
> <br>
|
||||
> commit 50775360ba3904c41e84176337ff47e6e7d6177c
|
||||
<!-- https://github.com/rust-lang/book/blob/main/src/ch08-03-hash-maps.md -->
|
||||
<!-- commit 5d22a358fb2380aa3f270d7b6269b67b8e44849e -->
|
||||
|
||||
最后介绍的常用集合类型是 **哈希 map**(*hash map*)。`HashMap<K, V>` 类型储存了一个键类型 `K` 对应一个值类型 `V` 的映射。它通过一个 **哈希函数**(*hashing function*)来实现映射,决定如何将键和值放入内存中。很多编程语言支持这种数据结构,不过通常有不同的名字:哈希、map、对象、哈希表或者关联数组,仅举几例。
|
||||
最后介绍的常用集合类型是**哈希 map**(*hash map*)。`HashMap<K, V>` 类型储存了一个键类型 `K` 对应一个值类型 `V` 的映射。它通过一个**哈希函数**(*hashing function*)来实现映射,决定如何将键和值放入内存中。很多编程语言支持这种数据结构,不过通常有不同的名字:**哈希**、**map**、**对象**、**哈希表**、**字典**或者**关联数组**,仅举几例。
|
||||
|
||||
哈希 map 可以用于需要任何类型作为键来寻找数据的情况,而不是像 vector 那样通过索引。例如,在一个游戏中,你可以将每个团队的分数记录到哈希 map 中,其中键是队伍的名字而值是每个队伍的分数。给出一个队名,就能得到他们的得分。
|
||||
哈希 map 可以用于需要任何类型作为键来寻找数据的情况,而不是像 vector 那样通过索引。例如,在一个游戏中,你可以将每个团队的分数记录到哈希 map 中,其中键是队伍的名字而值是每个队伍的分数。给出一个队名,就能检索到该队的得分。
|
||||
|
||||
本章我们会介绍哈希 map 的基本 API,不过还有更多吸引人的功能隐藏于标准库在 `HashMap<K, V>` 上定义的函数中。一如既往请查看标准库文档来了解更多信息。
|
||||
|
||||
### 新建一个哈希 map
|
||||
|
||||
可以使用 `new` 创建一个空的 `HashMap`,并使用 `insert` 增加元素。在示例 8-20 中我们记录两支队伍的分数,分别是蓝队和黄队。蓝队开始有 10 分而黄队开始有 50 分:
|
||||
可以使用 `new` 创建一个空的 `HashMap`,并使用 `insert` 增加元素。在示例 8-20 中我们记录两支队伍的分数,分别是**蓝队**和**黄队**。蓝队开始有 10 分而黄队开始有 50 分:
|
||||
|
||||
```rust
|
||||
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-20/src/main.rs:here}}
|
||||
|
Loading…
Reference in New Issue
Block a user