使用 use 关键字将路径引入作用域
Bringing Paths into Scope with the use Keyword
必须写出调用函数的完整路径可能会感到不便且重复。在示例 7-7 中,无论我们选择 add_to_waitlist 函数的绝对路径还是相对路径,每次想调用 add_to_waitlist 时,我们都必须指定 front_of_house 和 hosting。幸运的是,有一种方法可以简化这个过程:我们可以使用 use 关键字为路径创建一个快捷方式,然后在该作用域的其他任何地方使用这个较短的名称。
Having to write out the paths to call functions can feel inconvenient and
repetitive. In Listing 7-7, whether we chose the absolute or relative path to
the add_to_waitlist function, every time we wanted to call add_to_waitlist
we had to specify front_of_house and hosting too. Fortunately, there’s a
way to simplify this process: We can create a shortcut to a path with the use
keyword once and then use the shorter name everywhere else in the scope.
在示例 7-11 中,我们将 crate::front_of_house::hosting 模块引入 eat_at_restaurant 函数的作用域,这样我们在 eat_at_restaurant 中调用 add_to_waitlist 函数时只需指定 hosting::add_to_waitlist 即可。
In Listing 7-11, we bring the crate::front_of_house::hosting module into the
scope of the eat_at_restaurant function so that we only have to specify
hosting::add_to_waitlist to call the add_to_waitlist function in
eat_at_restaurant.
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
在作用域中添加 use 和路径类似于在文件系统中创建符号链接。通过在 crate root 中添加 use crate::front_of_house::hosting,hosting 现在在该作用域中是一个有效的名称,就像 hosting 模块是在 crate root 中定义的一样。通过 use 引入作用域的路径也会检查隐私性,就像任何其他路径一样。
Adding use and a path in a scope is similar to creating a symbolic link in
the filesystem. By adding use crate::front_of_house::hosting in the crate
root, hosting is now a valid name in that scope, just as though the hosting
module had been defined in the crate root. Paths brought into scope with use
also check privacy, like any other paths.
注意,use 只为 use 出现的特定作用域创建快捷方式。示例 7-12 将 eat_at_restaurant 函数移动到一个名为 customer 的新子模块中,该模块与 use 语句属于不同的作用域,因此函数体将无法编译。
Note that use only creates the shortcut for the particular scope in which the
use occurs. Listing 7-12 moves the eat_at_restaurant function into a new
child module named customer, which is then a different scope than the use
statement, so the function body won’t compile.
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use crate::front_of_house::hosting;
mod customer {
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
}
编译器错误显示快捷方式在 customer 模块内不再适用:
$ cargo build
Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0433]: failed to resolve: use of unresolved module or unlinked crate `hosting`
--> src/lib.rs:11:9
|
11 | hosting::add_to_waitlist();
| ^^^^^^^ use of unresolved module or unlinked crate `hosting`
|
= help: if you wanted to use a crate named `hosting`, use `cargo add hosting` to add it to your `Cargo.toml`
help: consider importing this module through its public re-export
|
10 + use crate::hosting;
|
warning: unused import: `crate::front_of_house::hosting`
--> src/lib.rs:7:5
|
7 | use crate::front_of_house::hosting;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `#[warn(unused_imports)]` on by default
For more information about this error, try `rustc --explain E0433`.
warning: `restaurant` (lib) generated 1 warning
error: could not compile `restaurant` (lib) due to 1 previous error; 1 warning emitted
注意还有一个警告,提示 use 在其作用域内不再被使用!要解决此问题,请将 use 也移动到 customer 模块内,或者在子 customer 模块中使用 super::hosting 引用父模块中的快捷方式。
Notice there’s also a warning that the use is no longer used in its scope! To
fix this problem, move the use within the customer module too, or reference
the shortcut in the parent module with super::hosting within the child
customer module.
创建惯用的 use 路径
Creating Idiomatic use Paths
在示例 7-11 中,你可能想知道为什么我们指定了 use crate::front_of_house::hosting 然后在 eat_at_restaurant 中调用 hosting::add_to_waitlist,而不是像示例 7-13 那样将 use 路径一直指定到 add_to_waitlist 函数来达到同样的结果。
In Listing 7-11, you might have wondered why we specified use crate::front_of_house::hosting and then called hosting::add_to_waitlist in
eat_at_restaurant, rather than specifying the use path all the way out to
the add_to_waitlist function to achieve the same result, as in Listing 7-13.
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use crate::front_of_house::hosting::add_to_waitlist;
pub fn eat_at_restaurant() {
add_to_waitlist();
}
虽然示例 7-11 和示例 7-13 完成了相同的任务,但示例 7-11 是将函数引入作用域的惯用方式。将函数的父模块引入作用域意味着我们在调用函数时必须指定父模块。在调用函数时指定父模块可以清楚地表明该函数不是本地定义的,同时仍能最大限度地减少完整路径的重复。示例 7-13 中的代码对于 add_to_waitlist 是在哪里定义的并不清楚。
Although both Listing 7-11 and Listing 7-13 accomplish the same task, Listing
7-11 is the idiomatic way to bring a function into scope with use. Bringing
the function’s parent module into scope with use means we have to specify the
parent module when calling the function. Specifying the parent module when
calling the function makes it clear that the function isn’t locally defined
while still minimizing repetition of the full path. The code in Listing 7-13 is
unclear as to where add_to_waitlist is defined.
另一方面,当通过 use 引入结构体、枚举和其他项时,惯用法是指定完整路径。示例 7-14 展示了将标准库的 HashMap 结构体引入二进制 crate 作用域的惯用方式。
On the other hand, when bringing in structs, enums, and other items with use,
it’s idiomatic to specify the full path. Listing 7-14 shows the idiomatic way
to bring the standard library’s HashMap struct into the scope of a binary
crate.
use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
map.insert(1, 2);
}
这个惯用法背后并没有强有力的理由:它只是随之出现的约定,人们已经习惯了以这种方式阅读和编写 Rust 代码。
There’s no strong reason behind this idiom: It’s just the convention that has emerged, and folks have gotten used to reading and writing Rust code this way.
这个惯用法的例外情况是,如果我们通过 use 语句将两个同名的项引入作用域,因为 Rust 不允许这样做。示例 7-15 展示了如何将两个同名但父模块不同的 Result 类型引入作用域,以及如何引用它们。
The exception to this idiom is if we’re bringing two items with the same name
into scope with use statements, because Rust doesn’t allow that. Listing 7-15 shows how to bring two Result` types into scope that have the same name but
different parent modules, and how to refer to them.
use std::fmt;
use std::io;
fn function1() -> fmt::Result {
// --snip--
Ok(())
}
fn function2() -> io::Result<()> {
// --snip--
Ok(())
}
如你所见,使用父模块可以区分这两个 Result 类型。如果相反我们指定 use std::fmt::Result 和 use std::io::Result,我们就会在同一个作用域内拥有两个 Result 类型,而 Rust 在我们使用 Result 时将不知道我们指的是哪一个。
As you can see, using the parent modules distinguishes the two Result types.
If instead we specified use std::fmt::Result and use std::io::Result, we’d
have two Result types in the same scope, and Rust wouldn’t know which one we
meant when we used Result.
使用 as 关键字提供新名称
Providing New Names with the as Keyword
对于将两个同名类型引入同一个作用域的问题,还有另一种解决方案:在路径之后,我们可以指定 as 和一个该类型的新本地名称(即别名,alias)。示例 7-16 展示了编写示例 7-15 代码的另一种方式,即使用 as 重命名两个 Result 类型中的一个。
There’s another solution to the problem of bringing two types of the same name
into the same scope with use: After the path, we can specify as and a new
local name, or alias, for the type. Listing 7-16 shows another way to write
the code in Listing 7-15 by renaming one of the two Result types using as.
use std::fmt::Result;
use std::io::Result as IoResult;
fn function1() -> Result {
// --snip--
Ok(())
}
fn function2() -> IoResult<()> {
// --snip--
Ok(())
}
在第二个 use 语句中,我们为 std::io::Result 类型选择了新名称 IoResult ,这不会与我们也引入作用域的来自 std::fmt 的 Result 发生冲突。示例 7-15 和示例 7-16 都被认为是惯用的,所以选择权在你!
In the second use statement, we chose the new name IoResult for the
std::io::Result type, which won’t conflict with the Result from std::fmt
that we’ve also brought into scope. Listing 7-15 and Listing 7-16 are
considered idiomatic, so the choice is up to you!
使用 pub use 重导出名称
Re-exporting Names with pub use
当我们使用 use 关键字将名称引入作用域时,该名称在导入它的作用域内是私有的。为了使该作用域之外的代码能够引用该名称,就像它是在该作用域内定义的一样,我们可以结合 pub 和 use。这种技术被称为重导出(re-exporting),因为我们不仅将一个项引入作用域,还让该项可供其他人引入其作用域。
When we bring a name into scope with the use keyword, the name is private to
the scope into which we imported it. To enable code outside that scope to refer
to that name as if it had been defined in that scope, we can combine pub and
use. This technique is called re-exporting because we’re bringing an item
into scope but also making that item available for others to bring into their
scope.
示例 7-17 显示了示例 7-11 中的代码,根模块中的 use 更改为 pub use。
Listing 7-17 shows the code in Listing 7-11 with use in the root module
changed to pub use.
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
pub use crate::front_of_house::hosting;
pub fn eat_at_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()。
Before this change, external code would have to call the add_to_waitlist
function by using the path
restaurant::front_of_house::hosting::add_to_waitlist(), which also would have
required the front_of_house module to be marked as pub. Now that this pub use has re-exported the hosting module from the root module, external code
can use the path restaurant::hosting::add_to_waitlist() instead.
当代码的内部结构与调用代码的程序员思考该领域的方式不同时,重导出非常有用。例如,在这个餐厅的比喻中,经营餐厅的人考虑“前台”和“后台”。但光顾餐厅的顾客可能不会从这些方面考虑餐厅的各个部分。通过 pub use,我们可以用一种结构编写代码,但暴露不同的结构。这样做可以使我们的库对于开发库的程序员和调用库的程序员都组织良好。我们将在第 14 章的“导出便捷的公共 API”中看到另一个 pub use 的例子,以及它如何影响 crate 的文档。
Re-exporting is useful when the internal structure of your code is different
from how programmers calling your code would think about the domain. For
example, in this restaurant metaphor, the people running the restaurant think
about “front of house” and “back of house.” But customers visiting a restaurant
probably won’t think about the parts of the restaurant in those terms. With pub use, we can write our code with one structure but expose a different structure.
Doing so makes our library well organized for programmers working on the library
and programmers calling the library. We’ll look at another example of pub use
and how it affects your crate’s documentation in “Exporting a Convenient Public
API” in Chapter 14.
使用外部 Package
Using External Packages
在第 2 章中,我们编写了一个猜谜游戏项目,它使用了一个名为 rand 的外部 package 来获取随机数。为了在我们的项目中使用 rand,我们在 Cargo.toml 中添加了这一行:
In Chapter 2, we programmed a guessing game project that used an external
package called rand to get random numbers. To use rand in our project, we
added this line to Cargo.toml:
rand = "0.8.5"
在 Cargo.toml 中添加 rand 作为依赖项,会告诉 Cargo 从 crates.io 下载 rand package 及其任何依赖项,并使 rand 对我们的项目可用。
Adding rand as a dependency in Cargo.toml tells Cargo to download the
rand package and any dependencies from crates.io and
make rand available to our project.
然后,为了将 rand 定义引入我们 package 的作用域,我们添加了一个以 crate 名称 rand 开头的 use 行,并列出了我们想引入作用域的项。回想在第 2 章“生成随机数”中,我们将 Rng trait 引入作用域并调用了 rand::thread_rng 函数:
Then, to bring rand definitions into the scope of our package, we added a
use line starting with the name of the crate, rand, and listed the items we
wanted to bring into scope. Recall that in “Generating a Random
Number” in Chapter 2, we brought the Rng trait into
scope and called the rand::thread_rng function:
use std::io;
use rand::Rng;
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("The secret number is: {secret_number}");
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
println!("You guessed: {guess}");
}
Rust 社区的成员在 crates.io 上提供了许多 package,将其中任何一个拉入你的 package 都涉及这些相同的步骤:在 package 的 Cargo.toml 文件中列出它们,并使用 use 将它们 crate 中的项引入作用域。
Members of the Rust community have made many packages available at
crates.io, and pulling any of them into your package
involves these same steps: listing them in your package’s Cargo.toml file and
using use to bring items from their crates into scope.
注意,标准库 std 也是我们 package 外部的一个 crate。因为标准库是随 Rust 语言一起交付的,所以我们不需要更改 Cargo.toml 来包含 std。但我们确实需要使用 use 来引用它,以便将其中的项引入我们的 package 作用域。例如,对于 HashMap,我们会使用这一行:
Note that the standard std library is also a crate that’s external to our
package. Because the standard library is shipped with the Rust language, we
don’t need to change Cargo.toml to include std. But we do need to refer to
it with use to bring items from there into our package’s scope. For example,
with HashMap we would use this line:
#![allow(unused)]
fn main() {
use std::collections::HashMap;
}
这是一个以 std(标准库 crate 的名称)开头的绝对路径。
This is an absolute path starting with std, the name of the standard library
crate.
使用嵌套路径清理大型 use 列表
Using Nested Paths to Clean Up use Lists
如果我们正在使用定义在同一个 crate 或同一个模块中的多个项,将每个项列在它自己的行上会占用文件中大量的垂直空间。例如,我们在示例 2-4 中的猜谜游戏中有这两个 use 语句将来自 std 的项引入作用域:
If we’re using multiple items defined in the same crate or same module, listing
each item on its own line can take up a lot of vertical space in our files. For
example, these two use statements we had in the guessing game in Listing 2-4
bring items from std into scope:
use rand::Rng;
// --snip--
use std::cmp::Ordering;
use std::io;
// --snip--
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("The secret number is: {secret_number}");
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
println!("You guessed: {guess}");
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}
}
相反,我们可以使用嵌套路径在一行中将相同的项引入作用域。我们通过指定路径的共同部分,后跟两个冒号,然后在花括号中列出路径中不同的部分来实现这一点,如示例 7-18 所示。
Instead, we can use nested paths to bring the same items into scope in one line. We do this by specifying the common part of the path, followed by two colons, and then curly brackets around a list of the parts of the paths that differ, as shown in Listing 7-18.
use rand::Rng;
// --snip--
use std::{cmp::Ordering, io};
// --snip--
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("The secret number is: {secret_number}");
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
let guess: u32 = guess.trim().parse().expect("Please type a number!");
println!("You guessed: {guess}");
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}
}
在较大的程序中,使用嵌套路径从同一个 crate 或模块引入许多项可以大大减少所需的独立 use 语句数量!
In bigger programs, bringing many items into scope from the same crate or
module using nested paths can reduce the number of separate use statements
needed by a lot!
我们可以在路径的任何层级使用嵌套路径,这在合并两个共享子路径的 use 语句时非常有用。例如,示例 7-19 显示了两个 use 语句:一个将 std::io 引入作用域,另一个将 std::io::Write 引入作用域。
We can use a nested path at any level in a path, which is useful when combining
two use statements that share a subpath. For example, Listing 7-19 shows two
use statements: one that brings std::io into scope and one that brings
std::io::Write into scope.
use std::io;
use std::io::Write;
这两个路径的共同部分是 std::io,而这正是完整的第一个路径。要将这两个路径合并为一个 use 语句,我们可以在嵌套路径中使用 self,如示例 7-20 所示。
The common part of these two paths is std::io, and that’s the complete first
path. To merge these two paths into one use statement, we can use self in
the nested path, as shown in Listing 7-20.
use std::io::{self, Write};
这一行将 std::io 和 std::io::Write 引入了作用域。
This line brings std::io and std::io::Write into scope.
使用 Glob 运算符导入项
Importing Items with the Glob Operator
如果我们想将路径中定义的“所有”公开项引入作用域,我们可以指定该路径,后跟 * glob 运算符:
If we want to bring all public items defined in a path into scope, we can
specify that path followed by the * glob operator:
#![allow(unused)]
fn main() {
use std::collections::*;
}
这个 use 语句将 std::collections 中定义的所有公开项引入当前作用域。使用 glob 运算符时要小心!Glob 会让你更难分辨哪些名称在作用域内,以及程序中使用的名称是在哪里定义的。此外,如果依赖项更改了其定义,你导入的内容也会随之更改,这可能会在你升级依赖项时导致编译器错误,例如,如果依赖项添加了一个与你在同一作用域内的定义同名的定义。
This use statement brings all public items defined in std::collections into
the current scope. Be careful when using the glob operator! Glob can make it
harder to tell what names are in scope and where a name used in your program
was defined. Additionally, if the dependency changes its definitions, what
you’ve imported changes as well, which may lead to compiler errors when you
upgrade the dependency if the dependency adds a definition with the same name
as a definition of yours in the same scope, for example.
Glob 运算符通常在测试时使用,用于将所有被测内容引入 tests 模块;我们将在第 11 章的“如何编写测试”中讨论。Glob 运算符有时也被用作 prelude(预导入)模式的一部分:有关该模式的更多信息,请参阅标准库文档。
The glob operator is often used when testing to bring everything under test into
the tests module; we’ll talk about that in “How to Write
Tests” in Chapter 11. The glob operator is also
sometimes used as part of the prelude pattern: See the standard library
documentation for more
information on that pattern.