引用模块树中项的路径
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 root 开始的全路径;对于来自外部 crate 的代码,绝对路径以 crate 名称开头,对于来自当前 crate 的代码,它以字面量
crate开头。 -
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. -
相对路径(relative path)从当前模块开始,并使用
self、super或当前模块中的标识符。 -
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 root 中定义的 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.
mod front_of_house {
mod hosting {
fn add_to_waitlist() {}
}
}
pub fn eat_at_restaurant() {
// Absolute path
crate::front_of_house::hosting::add_to_waitlist();
// Relative path
front_of_house::hosting::add_to_waitlist();
}
我们在 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 root 开始就像在 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.
$ cargo build
Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0603]: module `hosting` is private
--> src/lib.rs:9:28
|
9 | crate::front_of_house::hosting::add_to_waitlist();
| ^^^^^^^ --------------- function `add_to_waitlist` is not publicly re-exported
| |
| private module
|
note: the module `hosting` is defined here
--> src/lib.rs:2:5
|
2 | mod hosting {
| ^^^^^^^^^^^
error[E0603]: module `hosting` is private
--> src/lib.rs:12:21
|
12 | front_of_house::hosting::add_to_waitlist();
| ^^^^^^^ --------------- function `add_to_waitlist` is not publicly re-exported
| |
| private module
|
note: the module `hosting` is defined here
--> src/lib.rs:2:5
|
2 | mod hosting {
| ^^^^^^^^^^^
For more information about this error, try `rustc --explain E0603`.
error: could not compile `restaurant` (lib) due to 2 previous errors
错误信息显示模块 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
让我们回到示例 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.
mod front_of_house {
pub mod hosting {
fn add_to_waitlist() {}
}
}
// -- snip --
pub fn eat_at_restaurant() {
// Absolute path
crate::front_of_house::hosting::add_to_waitlist();
// Relative path
front_of_house::hosting::add_to_waitlist();
}
不幸的是,示例 7-5 中的代码仍然会导致编译器错误,如示例 7-6 所示。
Unfortunately, the code in Listing 7-5 still results in compiler errors, as shown in Listing 7-6.
$ cargo build
Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0603]: function `add_to_waitlist` is private
--> src/lib.rs:10:37
|
10 | crate::front_of_house::hosting::add_to_waitlist();
| ^^^^^^^^^^^^^^^ private function
|
note: the function `add_to_waitlist` is defined here
--> src/lib.rs:3:9
|
3 | fn add_to_waitlist() {}
| ^^^^^^^^^^^^^^^^^^^^
error[E0603]: function `add_to_waitlist` is private
--> src/lib.rs:13:30
|
13 | front_of_house::hosting::add_to_waitlist();
| ^^^^^^^^^^^^^^^ private function
|
note: the function `add_to_waitlist` is defined here
--> src/lib.rs:3:9
|
3 | fn add_to_waitlist() {}
| ^^^^^^^^^^^^^^^^^^^^
For more information about this error, try `rustc --explain E0603`.
error: could not compile `restaurant` (lib) due to 2 previous errors
发生了什么?在 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.
让我们也通过在 add_to_waitlist 函数定义前添加 pub 关键字将其设为公开,如示例 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.
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
// -- snip --
pub fn eat_at_restaurant() {
// Absolute path
crate::front_of_house::hosting::add_to_waitlist();
// Relative path
front_of_house::hosting::add_to_waitlist();
}
现在代码可以编译了!为了了解为什么在隐私规则方面添加 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 root 中。虽然 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,我们可以访问它的父模块,所以此函数调用有效!
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 root 开始,而是从 front_of_house 开始。front_of_house 模块与 eat_at_restaurant 定义在同一个模块中,因此从定义 eat_at_restaurant 的模块开始的相对路径有效。然后,由于 hosting 和 add_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.
拥有二进制和库的 package 的最佳实践
Best Practices for Packages with a Binary and a Library
我们提到一个 package 可以同时包含 src/main.rs 二进制 crate root 以及 src/lib.rs 库 crate root,默认情况下这两个 crate 都将拥有 package 名称。通常,采用这种包含库和二进制 crate 模式的 package,在二进制 crate 中只需包含足够的代码来启动一个调用库 crate 中定义代码的可执行文件。这使得其他项目可以从 package 提供的绝大多数功能中受益,因为库 crate 的代码可以被共享。
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.
模块树应该定义在 src/lib.rs 中。然后,任何公开项都可以在二进制 crate 中通过以 package 名称开头的路径来使用。二进制 crate 变成了库 crate 的使用者,就像一个完全外部的 crate 使用库 crate 一样:它只能使用公共 API。这有助于你设计一个良好的 API;你不仅是作者,你还是客户端!
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!
在第 12 章中,我们将演示这种组织实践,创建一个同时包含二进制 crate 和库 crate 的命令行程序。
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
我们可以通过在路径开头使用 super 来构造从父模块(而不是当前模块或 crate root)开始的相对路径。这就像以 .. 语法开始文件系统路径,表示转到父目录。使用 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.
fn deliver_order() {}
mod back_of_house {
fn fix_incorrect_order() {
cook_order();
super::deliver_order();
}
fn cook_order() {}
}
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
我们也可以使用 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.
mod back_of_house {
pub struct Breakfast {
pub toast: String,
seasonal_fruit: String,
}
impl Breakfast {
pub fn summer(toast: &str) -> Breakfast {
Breakfast {
toast: String::from(toast),
seasonal_fruit: String::from("peaches"),
}
}
}
}
pub fn eat_at_restaurant() {
// Order a breakfast in the summer with Rye toast.
let mut meal = back_of_house::Breakfast::summer("Rye");
// Change our mind about what bread we'd like.
meal.toast = String::from("Wheat");
println!("I'd like {} toast please", meal.toast);
// The next line won't compile if we uncomment it; we're not allowed
// to see or modify the seasonal fruit that comes with the meal.
// meal.seasonal_fruit = String::from("blueberries");
}
因为 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.
mod back_of_house {
pub enum Appetizer {
Soup,
Salad,
}
}
pub fn eat_at_restaurant() {
let order1 = back_of_house::Appetizer::Soup;
let order2 = back_of_house::Appetizer::Salad;
}
因为我们使 Appetizer 枚举变为公开,所以我们可以在 eat_at_restaurant 中使用 Soup 和 Salad 变体。
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,然后我们将展示如何结合 pub 和 use。
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.