Updated src/packages_crates_and_modules/paths.md'.

This commit is contained in:
Hector PENG
2026-03-15 11:59:44 +08:00
parent 58566982de
commit 90bb48dcc7
2 changed files with 76 additions and 81 deletions

View File

@@ -1,15 +1,15 @@
mod front_of_house {
mod hosting {
fn add_to_waitlist() {}
fn seat_at_table() {}
}
mod serving {
fn take_order() {}
fn serve_order() {}
fn take_payment() {}
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
pub fn eat_at_restaurant() {
// 绝对路径方式
crate::front_of_house::hosting::add_to_waitlist();
// 相对路径方式
front_of_house::hosting::add_to_waitlist();
}

View File

@@ -1,24 +1,19 @@
# 引用模组树中项目的路径
**Paths for Referring to an Item in the Module Tree**
为了展示给 Rust 于何处找到模组树中的项目,我们会使用路径,以我们在导览文件系统时我们使用路径的同一方式。为了调用函数,我们需要知道他的路径。
路径可以采用两种形式:
为告诉 Rust 在模组树中的何处,可以找到某个项目,我们就像在文件系统中一样,用到了路径。要调用某个函数,我们需要知道他的路径。
- *绝对路径an absolute path*,是自代码箱根开始的完整路径;对于外部代码箱中的代码,绝对路径以代码箱名字开头,而对于当前代码箱中的代码,绝对路径以字面值 `crate` 开头;
- *相对路径a relative path*,从当前模组开始,并使用 `self``super`,或当前模组中的标识符。
路径有两种形式:
绝对路径和相对路径都后跟一个或多个以双冒号(`::`)分隔的标识符。
- *绝对路径an absolute path*,是从代码箱根开始的完整路径;对于外部代码箱中的代码,绝对路径从代码箱名字开始,而对于当前代码箱中的代码,绝对路径从字面的 `crate` 开始;
回到 [清单 7-1](./defining_modules.md#listing_7-1),假设我们打算调用 `add_to_waitlist` 函数。这相当于询问:`add_to_waitlist` 函数的路径是什么?下面清单 7-3 包含清单 7-1其中移除了部分模组及函数。
- *相对路径a relative path* 从当前模组开始,并用到 `self``super` 关键字,或当前模组中的某个标识符
我们将展示从一个定义在代码箱根处的新函数 `eat_at_restaurant`,调用 `add_to_waitlist` 函数的两种方法。这两个路径都是正确的,但还存在另一个问题,将阻止这个示例按原样编译。稍后我们将解释原因
绝对路径和相对路径,后面都有一或多个用双冒号(`::`)分隔的标识符。
回到清单 7-1假设我们要调用 `add_too_waitlist` 函数。这等同于在询问:`add_to_waitlist` 函数的路径是什么?下面清单 7-3 包含了去掉了部分模组及函数的清单 7-1。
我们将展示两种从代码箱根处,定义的新函数 `eat_at_restaurant`,调用 `add_too_waitlist` 函数的方法。这两个路径都是正确的,但还存在另一将导致本示例无法按原样编译的问题。我们稍后会解释原因。
`eat_at_restaurant` 函数,是咱们库代码箱公共 API 的一部分,因此我们使用了 `pub` 关键字对其进行标记。在 [使用 pub 关键字暴露路径](#使用-pub-关键字暴露路径) 小节,我们将详细介绍 `pub`
`eat_at_restaurant` 函数属于咱们库代码箱公开 API 的一部分,因此我们以 `pub` 关键字标记他。在 [以 `pub` 关键字暴露路径](#以-pub-关键字暴露路径) 小节中,我们将深入有关 `pub` 的细节。
文件名:`src/lib.rs`
@@ -39,25 +34,25 @@ pub fn eat_at_restaurant() {
}
```
*清单 7-3使用绝对与相对路径调用 `add_to_waitlist` 函数*
<a name="listing_7-3"></a>
**清单 7-3**:使用绝对路径与相对路径调用 `add_to_waitlist` 函数
第一次在 `eat_at_restaurant` 中调用 `add_to_waitlist` 函数时,我们使用了绝对路径。`add_to_waitlist` 函数定义在与 `eat_at_restaurant` 相同的代码箱下,这意味着我们可使用 `crate` 关键字开始一个绝对路径。然后,我们包含各个后续模组,直到到达 `add_to_waitlist`。咱们可以想象一个有着相同结构的文件系统:我们会指定路径 `/front_of_house/hosting/add_to_waitlist` 来运行 `add_to_waitlist` 程序;使用名字 `crate` 从代码箱根开始,就像在咱们的 shell 下使用 `/` 从文件系统根目录开始一样。
一次调用 `eat_at_restaurant` `add_to_waitlist` 函数时,我们使用的是绝对路径。`add_too_waitlist` 函数与 `eat_at_restaurant`,定义在同一个代码箱中,这意味着我们可以使用 `crate` 关键字,来开始绝对路径。然后,我们逐个包含后续模组,直到找到 `add_to_waitlist`。咱们可以想象某种具有相同结构的文件系统:我们指定 `/front_of_house/hosting/add_to_waitlist` 路径,来运行 `add_to_waitlist` 程序;使用 `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_too_waitlist` 时,我们使用了相对路径。该路径以 `front_of_house` 开头,`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_too_waitlist` 的绝对路径,但相对路径仍然有效。但是,如果我们将 `eat_at_restaurant` 函数单独移到名为 `dining` 的模组中,那么 `add_too_waitlist` 调用的绝对路径将保持不变,但相对路径则需要更新。一般来说,我们更倾向于指定绝对路径,因为我们更有可能希望,独立地移动项目的代码定义和项目的调用
我们来试着编译清单 7-3看看他为什么还不能编译我们得到的错误信息如清单 7-4 所示。
我们来尝试编译清单 7-3并找出他为何还不编译我们得到的错误如下清单 7-4 中所示
```console
$ cargo build
Compiling restuarant v0.1.0 (/home/hector/restuarant)
Compiling restuarant v0.1.0 (/home/hector/rust-lang-zh_CN/projects/restuarant)
error[E0603]: module `hosting` is private
--> src/lib.rs:8:28
--> src/lib.rs:9:28
|
8 | crate::front_of_house::hosting::add_to_waitlist();
9 | crate::front_of_house::hosting::add_to_waitlist();
| ^^^^^^^ --------------- function `add_to_waitlist` is not publicly re-exported
| |
| private module
@@ -69,9 +64,9 @@ note: the module `hosting` is defined here
| ^^^^^^^^^^^
error[E0603]: module `hosting` is private
--> src/lib.rs:10:21
--> src/lib.rs:12:21
|
10 | front_of_house::hosting::add_to_waitlist();
12 | front_of_house::hosting::add_to_waitlist();
| ^^^^^^^ --------------- function `add_to_waitlist` is not publicly re-exported
| |
| private module
@@ -79,28 +74,26 @@ error[E0603]: module `hosting` is private
note: the module `hosting` is defined here
--> src/lib.rs:2:5
|
2 | mod hosting {
2 | mod hosting {
| ^^^^^^^^^^^
For more information about this error, try `rustc --explain E0603`.
error: could not compile `restuarant` (lib) due to 2 previous errors
```
*清单 7-4构建清单 7-3 中代码时的编译器报错*
<a name="listing_7-4"></a>
**清单 7-4**:构建清单 7-3 中代码时的编译器报错
错误消息表明模组 `hosting` 是私有的。换句话说,我们有 `hosting` 模组及 `add_to_waitlist` 函数的正确路径,但 Rust 将不允许我们使用他们,因为他没有对私有部分的权限。在 Rust 中,默认情况下所有项目(函数、方法、结构体、枚举、模组和常量等)都属于父模组私有。当咱们打算将函数或结构体等项目构造为私有时,咱们就将其放入模组中。
父模组中的项目不能使用子模组中的私有项目,但子模组中的项目可以使用其祖辈模组中的项目。这是因为子模组封装并隐藏了他们的实现细节,但子模组可以看到定义他们的上下文。继续我们的比喻,请把隐私规则想象成餐厅的后台:那里发生的事情对餐厅顾客来说属于私有,但办公室经理可以看到并执行他们运营餐厅里的一切。
Rust 选择让模组系统以这种方式运作从而隐藏内部实现细节成为默认行为。这样咱们就清楚咱们可以修改内部代码的哪些部分而不会破坏外部代码。不过Rust 确实为咱们提供了通过使用 `pub` 关键字构造项目为公开,以暴露子模组代码的内部部分给外部祖辈模组的选项。
错误消息表明,模组 `hosting` 是私有的。换句话说,我们有了 `hosting` 模组和 `add_too_waitlist` 函数的正确路径,但 Rust 不允许我们使用他们,因为其无法访问私有部分。在 Rust 中,所有项目(函数、方法、结构体、枚举、模组和常量),默认都是父模组私有的。如果咱们打算将函数或结构体等项目私有化,可以将其放入模组中。
## 以 `pub` 关键字暴露路径
父模组中的项目,不能使用子模组中的私有项目,但子模组中的项目,却可以使用其祖辈模组中的项目。这是因为子模组封装并隐藏了他们的实现细节,但子模组可以看到定义他们的上下文。继续我们的比喻,请把隐私规则,想象成某家餐厅的后台办公室:里面发生的事情,对餐厅顾客来说是隐私,但办公室经理,却可以看到执行做他们所经营餐厅里的一切事情
Rust 选择让模组系统以这种方式运行以便在默认情况下隐藏内部实现细节。这样咱们就明白在不破坏外部代码的情况下可以修改内部代码的哪些部分。不过Rust 确实提供了选项,让咱们可以通过使用 `pub` 关键字,将子模组的内部代码,公开给外部的祖辈模组。
## 使用 `pub` 关键字暴露路径
**Exposing Paths with the `pub` Keyword**
我们回到清单 7-4 中的报错,该报错告诉我们,`hosting` 模组是私有的。我们希望父模组中的 `eat_at_restaurant` 函数,能访问子模组中的 `add_too_waitlist` 函数,因此我们在 `hosting` 模组中,标记了 `pub` 关键字,如清单 7-5 所示。
我们来回到清单 7-4 中的报错,他告诉我们 `hosting` 模组是私有的。我们希望父模组中的 `eat_at_restaurant` 函数有着对子模组中 `add_too_waitlist` 函数的访问权限,因此我们以 `pub` 关键字标记 `hosting` 模组,如下清单 7-5 中所示
文件名:`src/lib.rs`
@@ -121,53 +114,56 @@ pub fn eat_at_restaurant() {
}
```
*清单 7-5将 `hosting` 模组声明为 `pub`,以便在 `eat_at_restaurant` 中使用他*
<a name="listing_7-5"></a>
**清单 7-5**:声明 `hosting` 模组为 `pub`,以在 `eat_at_restaurant` 中使用他
不幸的是,如下清单 7-6 所示,清单 7-5 中的代码仍会导致报错
不幸的是,清单 7-5 中的代码仍会导致编译器错误,如下清单 7-6 中所示
```console
$ cargo build
Compiling restaurant v0.1.0 (/home/peng/rust-lang/restaurant)
error[E0603]: function `add_to_waitlist` is private
--> src/lib.rs:9:37
Compiling restuarant v0.1.0 (/home/hector/rust-lang-zh_CN/projects/restuarant)
error[E0603]: module `hosting` is private
--> src/lib.rs:9:28
|
9 | crate::front_of_house::hosting::add_to_waitlist();
| ^^^^^^^^^^^^^^^ private function
| ^^^^^^^ --------------- function `add_to_waitlist` is not publicly re-exported
| |
| private module
|
note: the function `add_to_waitlist` is defined here
--> src/lib.rs:3:9
note: the module `hosting` is defined here
--> src/lib.rs:2:5
|
3 | fn add_to_waitlist() {}
| ^^^^^^^^^^^^^^^^^^^^
2 | mod hosting {
| ^^^^^^^^^^^
error[E0603]: function `add_to_waitlist` is private
--> src/lib.rs:12:30
error[E0603]: module `hosting` is private
--> src/lib.rs:12:21
|
12 | front_of_house::hosting::add_to_waitlist();
| ^^^^^^^^^^^^^^^ private function
| ^^^^^^^ --------------- function `add_to_waitlist` is not publicly re-exported
| |
| private module
|
note: the function `add_to_waitlist` is defined here
--> src/lib.rs:3:9
note: the module `hosting` is defined here
--> src/lib.rs:2:5
|
3 | fn add_to_waitlist() {}
| ^^^^^^^^^^^^^^^^^^^^
2 | mod hosting {
| ^^^^^^^^^^^
For more information about this error, try `rustc --explain E0603`.
error: could not compile `restaurant` due to 2 previous errors
error: could not compile `restuarant` (lib) due to 2 previous errors
```
*清单 7-6构建清单 7-5 中代码时出现的编译器报错*
*清单 7-6构建清单 7-5 中代码时的编译器报错*
发生了什么事?在 `mod hosting` 前添加 `pub` 关键字会构造该模组为公开。在这一修改下,当我们可以访问 `front_of_house` 时,我们也可以访问 `hosting`。但 `hosting`*内容* 仍然是私有的;构造模组为公开不会构造其内容为公开。模组上的 `pub` 关键字只会让其祖辈模组中的代码可以引用他,而不是访问其内部代码。因为模组属于容器,所以仅构造模组为公开并不能做得更多;我们需要更进一步,选择构造模组内的一个或多个项目为公开。
发生了什么?在 `hosting` 模组前添加 `pub` 关键字后,该模组就变成了公共模组。有了这个改动,如果我们能访问 `front_of_house`,也就能访问 `hosting`。但是,`hosting`*内容* 仍然是私有的;将该模组构造为公开,并不会使其内容公开。模组上的 `pub` 关键字,只能让其先辈模组中的代码引用他,而不能访问其内部代码。因为模组是个容器,所以只将模组公开并不能做什么;我们需要更进一步,选择将其模组中的一或多个项目也公开
清单 7-6 中的报错表明 `add_to_waitlist` 函数是私有的。隐私规则适用于结构体、枚举、函数和方法以及模组等
清单 7-6 中的报错表明,`add_too_waitlist` 函数是私有的。隐私规则适用于结构体、枚举、函数和方法以及模组
我们还可通过在 `add_too_waitlist` 函数的定义前添加 `pub` 关键字,构造该函数为公开,如下清单 7-7 中所示
我们还可以在 `add_too_waitlist` 函数的定义前,添加 `pub` 关键字,使其成为公共函数,如清单 7-7 所示。
<a name="list_7-7"></a>
文件名:`src/lib.rs`
```rust
@@ -186,20 +182,19 @@ pub fn eat_at_restaurant() {
}
```
*清单 7-7在 `mod hosting` 和 `fn add_too_waitlist` 中添加 `pub` 关键字后,我们就可以在 `eat_at_restaurant` 中调用了这个函数*
<a name="listing_7-7"></a>
**清单 7-7**:添加 `pub` 关键字到 `mod hosting``fn add_too_waitlist`,让我们可以在 `eat_at_restaurant` 中调用这个函数
现在代码可以编译了!要了解为何添加 `pub` 关键字,我们可以在 `add_too_waitlist` 中,在遵守隐私规则下使用这些路径,我们来看看其中的绝对路径和相对路径。
现在这段代码将编译!为了了解为何添加 `pub` 关键字,就让我们可以在 `add_too_waitlist` 中,在遵守隐私规则下使用这些路径,我们来看一下绝对路径和相对路径。
在绝对路径中,我们 `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` 函数定义在`front_of_house` 模组同一个模组中(`eat_at_restaurant``front_of_house` 属于同辈),我们可在 `eat_at_restaurant` 中引用 `front_of_house`。接下来是 `pub` 标注`hosting` 模组。我们可以访问 `hosting` 的父模组,因此我们可以访问 `hosting`。最后,`add_to_waitlist` 函数 `pub` 标注了,并且我们可以访问他的父模组,因此这个函数调用有效!
在相对路径中,除了第一步外,逻辑与绝对路径相同的:路径从代码箱根开始,而是从 `front_of_house` 开始。`front_of_house` 模组与 `eat_at_restaurant`,定义在同一个模组中,因此从定义出 `eat_at_restaurant` 的模组开始的相对路径是可行的。然后,由于 `hosting``add_to_waitlist` 被标记为了 `pub`,因此该路径的其余部分也有效,这个函数调用也就有效
在相对路径中,除了第一步外,逻辑与绝对路径相同的:路径从代码箱根开始,而是从 `front_of_house` 开始。`front_of_house` 模组定义在`eat_at_restaurant`同一个模组中,因此从 `eat_at_restaurant` 定义处的模组开始的相对路径是可行的。然后,因为 `hosting``add_to_waitlist` 都以 `pub` 标注过,因此该路径的其余部分也有效,进而这个函数调用有效!
咱们计划分享出咱们的库代码箱,以便其他项目可以使用咱们的代码,那么我们的公共 API就是咱们与代码箱用户之间的合约,决定了用户如何与咱们的代码交互。而在管理公开 API 变更方面,为使对咱们代码箱有依赖的人们更容易一些,则需要考虑多因素。这些考量超出了本书的范围;如果你对这个主题感兴趣,请参阅 [Rust API 指南](https://rust-lang.github.io/api-guidelines/)。
咱们计划共享咱们的库代码箱,以便其他项目可以使用咱们的代码,我们的公共 API 就是咱们与咱们代码箱用户的合约,决定了他们如何与咱们的代码交互。为了让人们更容易依赖咱们的代码箱,管理咱们 API 变更方面需要考虑多因素。这些考量超出了本书的范围;若咱们对这个主题感兴趣,请参阅 [Rust API 指南](https://rust-lang.github.io/api-guidelines/)。
> **带有一个二进制与一个库代码箱的 Rust 软件包最佳实践**
>
> **Best Practice for Packages with a Binary and a Library**
> **有着二进制与库的包的最佳实践**
>
> 我们曾提到过,一个软件包可以同时包含一个 `src/main.rs` 的二进制代码箱根,和一个 `src/lib.rs` 库代码箱根,且默认情况下这两个代码箱都将有着这个软件包的名字。通常情况下,以这种同时包含一个库和一个二进制代码箱模式的软件包,都会在二进制代码箱中,加入启动一个会调用到库代码箱的可执行文件的足够少代码。这样,其他项目就能从这个软件包所提供的大部分功能中获益,因为库代码箱的代码可以共用。
>