wip: 2024 edition

This commit is contained in:
KaiserY 2025-05-20 23:38:30 +08:00
parent 51c254a36e
commit d1e0ab6569
6 changed files with 60 additions and 62 deletions

View File

@ -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)

View File

@ -5,7 +5,7 @@
当你编写大型程序时,组织代码显得尤为重要。通过对相关功能进行分组和划分不同功能的代码,你可以清楚在哪里可以找到实现了特定功能的代码,以及在哪里可以改变一个功能的工作方式。
到目前为止我们编写的程序都在一个文件的一个模块中。伴随着项目的增长你应该通过将代码分解为多个模块和多个文件来组织代码。一个包package可以包含多个二进制 crate 项和一个可选的库 crate 。伴随着包的增长,你可以将包中的部分代码提取出来,做成独立的 crate这些 crate 则作为外部依赖项。本章将会涵盖所有这些概念。对于一个由一系列相互关联的包组成的超大型项目Cargo 提供了**工作空间***workspaces*)这一功能,我们将在第十四章的 [“Cargo Workspaces”][workspaces] 对此进行讲解。
到目前为止我们编写的程序都在一个文件的一个模块中。伴随着项目的增长你应该通过将代码分解为多个模块和多个文件来组织代码。一个包package可以包含多个二进制 crate 项和一个可选的库 crate。伴随着包的增长你可以将包中的部分代码提取出来做成独立的 crate这些 crate 则作为外部依赖项。本章将会涵盖所有这些概念。对于一个由一系列相互关联的包组成的超大型项目Cargo 提供了**工作空间***workspaces*)这一功能,我们将在第十四章的 [“Cargo Workspaces”][workspaces] 对此进行讲解。
我们也会讨论封装来实现细节,这可以让你在更高层面重用代码:你实现了一个操作后,其他的代码可以通过该代码的公共接口来进行调用,而不需要知道它是如何实现的。你在编写代码时可以定义哪些部分是其他代码可以使用的公共部分,以及哪些部分是你有权更改实现细节的私有部分。这是另一种减少你在脑海中记住项目内容数量的方法。

View File

@ -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无论是库的还是二进制的

View File

@ -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` 的隐式模块下。
这个模块树可能会令你想起电脑上文件系统的目录树;这是一个非常恰当的类比!就像文件系统的目录,你可以使用模块来组织你的代码。并且,就像目录中的文件,我们需要一种方法来找到模块。

View File

@ -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/

View File

@ -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>