Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help


x-i18n: generated_at: “2026-03-01T13:54:06Z” model: gemini-3-flash-preview provider: google-gemini-cli source_hash: ed9bb2ccb89214a0a47050c4870105a36916c35e634ea68486e5cc669804286b source_path: ch07-03-paths-for-referring-to-an-item-in-the-module-tree.md workflow: 16

在模块树中引用项的路径 (Paths for Referring to an Item in the Module Tree)

Paths for Referring to an Item in the Module Tree

为了向 Rust 展示在模块树的哪里可以找到一个项,我们使用路径,就像在导航文件系统时使用路径一样。要调用一个函数,我们需要知道它的路径。

To show Rust where to find an item in a module tree, we use a path in the same way we use a path when navigating a filesystem. To call a function, we need to know its path.

路径有两种形式:

A path can take two forms:

  • 绝对路径 (absolute path) 是从 crate 根开始的完整路径;对于来自外部 crate 的代码,绝对路径以 crate 名称开始,对于来自当前 crate 的代码,它以字面量 crate 开始。

  • 相对路径 (relative path) 从当前模块开始,并使用 selfsuper 或当前模块中的标识符。

  • An absolute path is the full path starting from a crate root; for code from an external crate, the absolute path begins with the crate name, and for code from the current crate, it starts with the literal crate.

  • A relative path starts from the current module and uses self, super, or an identifier in the current module.

绝对路径和相对路径后面都跟着一个或多个由双冒号 (::) 分隔的标识符。

Both absolute and relative paths are followed by one or more identifiers separated by double colons (::).

回到示例 7-1,假设我们要调用 add_to_waitlist 函数。这相当于在问:add_to_waitlist 函数的路径是什么?示例 7-3 包含了示例 7-1 并删除了一些模块和函数。

Returning to Listing 7-1, say we want to call the add_to_waitlist function. This is the same as asking: What’s the path of the add_to_waitlist function? Listing 7-3 contains Listing 7-1 with some of the modules and functions removed.

我们将展示两种在 crate 根定义的 eat_at_restaurant 新函数中调用 add_to_waitlist 函数的方法。这些路径是正确的,但仍存在另一个问题,将导致此示例目前无法编译。我们稍后会解释原因。

We’ll show two ways to call the add_to_waitlist function from a new function, eat_at_restaurant, defined in the crate root. These paths are correct, but there’s another problem remaining that will prevent this example from compiling as is. We’ll explain why in a bit.

eat_at_restaurant 函数是我们库 crate 公有 API 的一部分,所以我们用 pub 关键字标记它。在“使用 pub 关键字公开路径”部分,我们将详细介绍 pub

The eat_at_restaurant function is part of our library crate’s public API, so we mark it with the pub keyword. In the “Exposing Paths with the pub Keyword” section, we’ll go into more detail about pub.

{{#rustdoc_include ../listings/ch07-managing-growing-projects/listing-07-03/src/lib.rs}}

第一次在 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 中使用 / 从文件系统根开始一样。

The first time we call the add_to_waitlist function in eat_at_restaurant, we use an absolute path. The add_to_waitlist function is defined in the same crate as eat_at_restaurant, which means we can use the crate keyword to start an absolute path. We then include each of the successive modules until we make our way to add_to_waitlist. You can imagine a filesystem with the same structure: We’d specify the path /front_of_house/hosting/add_to_waitlist to run the add_to_waitlist program; using the crate name to start from the crate root is like using / to start from the filesystem root in your shell.

第二次在 eat_at_restaurant 中调用 add_to_waitlist 时,我们使用了相对路径。该路径以 front_of_house 开始,这是定义在与 eat_at_restaurant 相同模块树级别的模块名称。在这里,文件系统的等效表达是使用路径 front_of_house/hosting/add_to_waitlist。以模块名称开始意味着该路径是相对的。

The second time we call add_to_waitlist in eat_at_restaurant, we use a relative path. The path starts with front_of_house, the name of the module defined at the same level of the module tree as eat_at_restaurant. Here the filesystem equivalent would be using the path front_of_house/hosting/add_to_waitlist. Starting with a module name means that the path is relative.

选择使用相对路径还是绝对路径是你根据项目做出的决定,这取决于你更倾向于将项定义代码与使用该项的代码分开移动还是移动到一起。例如,如果我们把 front_of_house 模块和 eat_at_restaurant 函数移动到一个名为 customer_experience 的模块中,我们需要更新 add_to_waitlist 的绝对路径,但相对路径仍然有效。然而,如果我们将 eat_at_restaurant 函数单独移动到一个名为 dining 的模块中,对 add_to_waitlist 调用的绝对路径将保持不变,但需要更新相对路径。我们通常倾向于指定绝对路径,因为我们更有可能想要独立地移动代码定义和项调用。

Choosing whether to use a relative or absolute path is a decision you’ll make based on your project, and it depends on whether you’re more likely to move item definition code separately from or together with the code that uses the item. For example, if we moved the front_of_house module and the eat_at_restaurant function into a module named customer_experience, we’d need to update the absolute path to add_to_waitlist, but the relative path would still be valid. However, if we moved the eat_at_restaurant function separately into a module named dining, the absolute path to the add_to_waitlist call would stay the same, but the relative path would need to be updated. Our preference in general is to specify absolute paths because it’s more likely we’ll want to move code definitions and item calls independently of each other.

让我们尝试编译示例 7-3 并找出它目前为什么无法编译!我们得到的错误如示例 7-4 所示。

Let’s try to compile Listing 7-3 and find out why it won’t compile yet! The errors we get are shown in Listing 7-4.

{{#include ../listings/ch07-managing-growing-projects/listing-07-03/output.txt}}

错误消息显示模块 hosting 是私有的。换句话说,我们有 hosting 模块和 add_to_waitlist 函数的正确路径,但 Rust 不允许我们使用它们,因为它无法访问私有部分。在 Rust 中,默认情况下所有项(函数、方法、结构体、枚举、模块和常量)对父模块都是私有的。如果你想让一个像函数或结构体这样的项成为私有的,你就把它放在一个模块里。

The error messages say that module hosting is private. In other words, we have the correct paths for the hosting module and the add_to_waitlist function, but Rust won’t let us use them because it doesn’t have access to the private sections. In Rust, all items (functions, methods, structs, enums, modules, and constants) are private to parent modules by default. If you want to make an item like a function or struct private, you put it in a module.

父模块中的项不能使用子模块内部的私有项,但子模块中的项可以使用其祖先模块中的项。这是因为子模块包装并隐藏了其实现细节,但子模块可以看见它们被定义的上下文。继续我们的比喻,把私有性规则想象成餐厅的后台办公室:里面发生的事情对餐厅顾客是私有的,但办公室经理可以看见并操作他们经营的餐厅里的一切。

Items in a parent module can’t use the private items inside child modules, but items in child modules can use the items in their ancestor modules. This is because child modules wrap and hide their implementation details, but the child modules can see the context in which they’re defined. To continue with our metaphor, think of the privacy rules as being like the back office of a restaurant: What goes on in there is private to restaurant customers, but office managers can see and do everything in the restaurant they operate.

Rust 选择让模块系统以这种方式运行,以便隐藏内部实现细节是默认行为。这样你就知道可以更改内部代码的哪些部分而不会破坏外部代码。然而,Rust 确实为你提供了选项,通过使用 pub 关键字使一个项成为公有的,从而将子模块代码的内部部分暴露给外部祖先模块。

Rust chose to have the module system function this way so that hiding inner implementation details is the default. That way, you know which parts of the inner code you can change without breaking the outer code. However, Rust does give you the option to expose inner parts of child modules’ code to outer ancestor modules by using the pub keyword to make an item public.

使用 pub 关键字公开路径 (Exposing Paths with the pub Keyword)

Exposing Paths with the pub Keyword

让我们回到示例 7-4 中告诉我们 hosting 模块是私有的错误。我们希望父模块中的 eat_at_restaurant 函数能够访问子模块中的 add_to_waitlist 函数,因此我们用 pub 关键字标记 hosting 模块,如示例 7-5 所示。

Let’s return to the error in Listing 7-4 that told us the hosting module is private. We want the eat_at_restaurant function in the parent module to have access to the add_to_waitlist function in the child module, so we mark the hosting module with the pub keyword, as shown in Listing 7-5.

{{#rustdoc_include ../listings/ch07-managing-growing-projects/listing-07-05/src/lib.rs:here}}

不幸的是,示例 7-5 中的代码仍然导致编译器错误,如示例 7-6 所示。

Unfortunately, the code in Listing 7-5 still results in compiler errors, as shown in Listing 7-6.

{{#include ../listings/ch07-managing-growing-projects/listing-07-05/output.txt}}

发生了什么?在 mod hosting 前面添加 pub 关键字使该模块成为公有的。通过这种更改,如果我们能访问 front_of_house,我们就能访问 hosting。但是 hosting 的“内容”仍然是私有的;使模块公有并不会使其内容也公有。模块上的 pub 关键字仅允许其祖先模块中的代码引用它,而不是访问其内部代码。因为模块是容器,仅使模块公有能做的事情不多;我们需要更进一步,选择使模块内的一个或多个项也成为公有的。

What happened? Adding the pub keyword in front of mod hosting makes the module public. With this change, if we can access front_of_house, we can access hosting. But the contents of hosting are still private; making the module public doesn’t make its contents public. The pub keyword on a module only lets code in its ancestor modules refer to it, not access its inner code. Because modules are containers, there’s not much we can do by only making the module public; we need to go further and choose to make one or more of the items within the module public as well.

示例 7-6 中的错误显示 add_to_waitlist 函数是私有的。私有性规则适用于结构体、枚举、函数和方法,也适用于模块。

The errors in Listing 7-6 say that the add_to_waitlist function is private. The privacy rules apply to structs, enums, functions, and methods as well as modules.

让我们也通过在其定义前添加 pub 关键字使 add_to_waitlist 函数成为公有的,如示例 7-7 所示。

Let’s also make the add_to_waitlist function public by adding the pub keyword before its definition, as in Listing 7-7.

{{#rustdoc_include ../listings/ch07-managing-growing-projects/listing-07-07/src/lib.rs:here}}

现在代码将可以编译!为了看看添加 pub 关键字如何让我们在遵循私有性规则的情况下在 eat_at_restaurant 中使用这些路径,让我们看看绝对路径和相对路径。

Now the code will compile! To see why adding the pub keyword lets us use these paths in eat_at_restaurant with respect to the privacy rules, let’s look at the absolute and the relative paths.

在绝对路径中,我们从 crate 开始,它是 crate 模块树的根。front_of_house 模块定义在 crate 根中。虽然 front_of_house 不是公有的,但由于 eat_at_restaurant 函数与 front_of_house 定义在同一个模块中(也就是说,eat_at_restaurantfront_of_house 是兄弟),我们可以从 eat_at_restaurant 引用 front_of_house。接着是标记为 pubhosting 模块。我们可以访问 hosting 的父模块,所以我们可以访问 hosting。最后,add_to_waitlist 函数被标记为 pub,我们可以访问它的父模块,所以这个函数调用有效!

In the absolute path, we start with crate, the root of our crate’s module tree. The front_of_house module is defined in the crate root. While front_of_house isn’t public, because the eat_at_restaurant function is defined in the same module as front_of_house (that is, eat_at_restaurant and front_of_house are siblings), we can refer to front_of_house from eat_at_restaurant. Next is the hosting module marked with pub. We can access the parent module of hosting, so we can access hosting. Finally, the add_to_waitlist function is marked with pub, and we can access its parent module, so this function call works!

在相对路径中,逻辑与绝对路径相同,除了第一步:路径不是从 crate 根开始,而是从 front_of_house 开始。front_of_house 模块定义在与 eat_at_restaurant 相同的模块内,所以从定义 eat_at_restaurant 的模块开始的相对路径是有效的。然后,因为 hostingadd_to_waitlist 被标记为 pub,路径的其余部分有效,并且此函数调用也是有效的!

In the relative path, the logic is the same as the absolute path except for the first step: Rather than starting from the crate root, the path starts from front_of_house. The front_of_house module is defined within the same module as eat_at_restaurant, so the relative path starting from the module in which eat_at_restaurant is defined works. Then, because hosting and add_to_waitlist are marked with pub, the rest of the path works, and this function call is valid!

如果你计划分享你的库 crate 以便其他项目可以使用你的代码,你的公有 API 就是你与 crate 使用者之间的契约,它决定了他们如何与你的代码进行交互。关于管理公有 API 的更改以使人们更容易依赖你的 crate,有很多方面的考虑。这些考虑超出了本书的范围;如果你对此话题感兴趣,请参阅 Rust API 指南

If you plan to share your library crate so that other projects can use your code, your public API is your contract with users of your crate that determines how they can interact with your code. There are many considerations around managing changes to your public API to make it easier for people to depend on your crate. These considerations are beyond the scope of this book; if you’re interested in this topic, see the Rust API Guidelines.

包含二进制文件和库的包的最佳实践

我们提到一个包可以同时包含一个 src/main.rs 二进制 crate 根以及一个 src/lib.rs 库 crate 根,默认情况下,两个 crate 都将具有该包的名称。通常,具有这种同时包含库和二进制 crate 模式的包,在二进制 crate 中只需包含足够的代码来启动一个调用库 crate 中定义的代码的可执行文件。这使得其他项目可以从该包提供的最多功能中受益,因为库 crate 的代码可以被共享。

模块树应该在 src/lib.rs 中定义。然后,任何公有项都可以在二进制 crate 中通过以包名开头的路径来使用。二进制 crate 变成了库 crate 的使用者,就像一个完全外部的 crate 使用该库 crate 一样:它只能使用公有 API。这有助于你设计一个良好的 API;你不仅是作者,也是客户端!

第 12 章中,我们将通过一个同时包含二进制 crate 和库 crate 的命令行程序来演示这种组织实践。

Best Practices for Packages with a Binary and a Library

We mentioned that a package can contain both a src/main.rs binary crate root as well as a src/lib.rs library crate root, and both crates will have the package name by default. Typically, packages with this pattern of containing both a library and a binary crate will have just enough code in the binary crate to start an executable that calls code defined in the library crate. This lets other projects benefit from the most functionality that the package provides because the library crate’s code can be shared.

The module tree should be defined in src/lib.rs. Then, any public items can be used in the binary crate by starting paths with the name of the package. The binary crate becomes a user of the library crate just like a completely external crate would use the library crate: It can only use the public API. This helps you design a good API; not only are you the author, but you’re also a client!

In Chapter 12, we’ll demonstrate this organizational practice with a command line program that will contain both a binary crate and a library crate.

super 开始相对路径 (Starting Relative Paths with super)

Starting Relative Paths with super

我们可以通过在路径开头使用 super 来构建从父模块开始(而不是当前模块或 crate 根)的相对路径。这就像以 .. 语法开始一个文件系统路径,表示进入父目录。使用 super 允许我们引用我们知道在父模块中的项,这可以在模块与父模块紧密相关但父模块将来可能会被移动到模块树其他位置时,使重新组织模块树变得更容易。

We can construct relative paths that begin in the parent module, rather than the current module or the crate root, by using super at the start of the path. This is like starting a filesystem path with the .. syntax that means to go to the parent directory. Using super allows us to reference an item that we know is in the parent module, which can make rearranging the module tree easier when the module is closely related to the parent but the parent might be moved elsewhere in the module tree someday.

考虑示例 7-8 中的代码,它模拟了厨师修正错误的订单并亲自将其送到顾客面前的情况。在 back_of_house 模块中定义的 fix_incorrect_order 函数通过指定以 super 开头的 deliver_order 路径,调用了在父模块中定义的 deliver_order 函数。

Consider the code in Listing 7-8 that models the situation in which a chef fixes an incorrect order and personally brings it out to the customer. The function fix_incorrect_order defined in the back_of_house module calls the function deliver_order defined in the parent module by specifying the path to deliver_order, starting with super.

{{#rustdoc_include ../listings/ch07-managing-growing-projects/listing-07-08/src/lib.rs}}

fix_incorrect_order 函数位于 back_of_house 模块中,所以我们可以使用 super 进入 back_of_house 的父模块,在这种情况下是 crate(根)。从那里,我们寻找 deliver_order 并找到了它。成功!我们认为 back_of_house 模块和 deliver_order 函数很可能保持相互之间的这种关系,如果我们决定重组 crate 的模块树,它们会被一起移动。因此,我们使用了 super,这样如果以后代码被移动到不同的模块,我们需要更新代码的地方就会更少。

The fix_incorrect_order function is in the back_of_house module, so we can use super to go to the parent module of back_of_house, which in this case is crate, the root. From there, we look for deliver_order and find it. Success! We think the back_of_house module and the deliver_order function are likely to stay in the same relationship to each other and get moved together should we decide to reorganize the crate’s module tree. Therefore, we used super so that we’ll have fewer places to update code in the future if this code gets moved to a different module.

使结构体和枚举成为公有的 (Making Structs and Enums Public)

Making Structs and Enums Public

我们也可以使用 pub 将结构体和枚举指定为公有的,但在结构体和枚举中使用 pub 有一些额外的细节。如果在结构体定义前使用 pub,我们使结构体成为公有的,但结构体的字段仍将是私有的。我们可以根据具体情况决定是否使每个字段公有。在示例 7-9 中,我们定义了一个公有的 back_of_house::Breakfast 结构体,带有一个公有的 toast 字段,但有一个私有的 seasonal_fruit 字段。这模拟了餐厅里的情况:顾客可以选择随餐附送的面包类型,但厨师根据当季产品和库存情况决定随餐附送哪种水果。由于水果供应变化很快,顾客无法选择水果,甚至看不到他们将得到哪种水果。

We can also use pub to designate structs and enums as public, but there are a few extra details to the usage of pub with structs and enums. If we use pub before a struct definition, we make the struct public, but the struct’s fields will still be private. We can make each field public or not on a case-by-case basis. In Listing 7-9, we’ve defined a public back_of_house::Breakfast struct with a public toast field but a private seasonal_fruit field. This models the case in a restaurant where the customer can pick the type of bread that comes with a meal, but the chef decides which fruit accompanies the meal based on what’s in season and in stock. The available fruit changes quickly, so customers can’t choose the fruit or even see which fruit they’ll get.

{{#rustdoc_include ../listings/ch07-managing-growing-projects/listing-07-09/src/lib.rs}}

因为 back_of_house::Breakfast 结构体中的 toast 字段是公有的,在 eat_at_restaurant 中,我们可以使用点号表示法对 toast 字段进行读写。请注意,在 eat_at_restaurant 中我们不能使用 seasonal_fruit 字段,因为 seasonal_fruit 是私有的。尝试取消注释修改 seasonal_fruit 字段值的行,看看会得到什么错误!

Because the toast field in the back_of_house::Breakfast struct is public, in eat_at_restaurant we can write and read to the toast field using dot notation. Notice that we can’t use the seasonal_fruit field in eat_at_restaurant, because seasonal_fruit is private. Try uncommenting the line modifying the seasonal_fruit field value to see what error you get!

另外,请注意,因为 back_of_house::Breakfast 有一个私有字段,该结构体需要提供一个公有的关联函数来构造 Breakfast 的实例(我们在这里将其命名为 summer)。如果 Breakfast 没有这样一个函数,我们就无法在 eat_at_restaurant 中创建 Breakfast 的实例,因为我们无法在 eat_at_restaurant 中设置私有字段 seasonal_fruit 的值。

Also, note that because back_of_house::Breakfast has a private field, the struct needs to provide a public associated function that constructs an instance of Breakfast (we’ve named it summer here). If Breakfast didn’t have such a function, we couldn’t create an instance of Breakfast in eat_at_restaurant, because we couldn’t set the value of the private seasonal_fruit field in eat_at_restaurant.

相比之下,如果我们使一个枚举成为公有的,那么它的所有变体都是公有的。我们只需在 enum 关键字前加上 pub,如示例 7-10 所示。

In contrast, if we make an enum public, all of its variants are then public. We only need the pub before the enum keyword, as shown in Listing 7-10.

{{#rustdoc_include ../listings/ch07-managing-growing-projects/listing-07-10/src/lib.rs}}

因为我们使 Appetizer 枚举成为公有的,所以我们可以在 eat_at_restaurant 中使用 SoupSalad 变体。

Because we made the Appetizer enum public, we can use the Soup and Salad variants in eat_at_restaurant.

除非枚举的变体是公有的,否则枚举并无太大用处;如果在每种情况下都必须用 pub 来标注所有的枚举变体,那将非常烦人,因此枚举变体的默认设置是公有的。结构体通常在字段不公有的情况下也是有用的,因此结构体字段遵循通用的默认私有规则,除非标注了 pub

Enums aren’t very useful unless their variants are public; it would be annoying to have to annotate all enum variants with pub in every case, so the default for enum variants is to be public. Structs are often useful without their fields being public, so struct fields follow the general rule of everything being private by default unless annotated with pub.

关于 pub 还有一种我们尚未涵盖的情况,那就是我们的最后一个模块系统功能:use 关键字。我们将首先单独介绍 use,然后展示如何组合 pubuse

There’s one more situation involving pub that we haven’t covered, and that is our last module system feature: the use keyword. We’ll cover use by itself first, and then we’ll show how to combine pub and use.