使用 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
函数。
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
将模块引入作用域在作用域中添加 use
和路径类似于在文件系统中创建符号链接。通过在 crate 根目录中添加 use crate::front_of_house::hosting
,hosting
现在在该作用域中是一个有效的名称,就像 hosting
模块已在 crate 根目录中定义一样。使用 use
引入作用域的路径也会像任何其他路径一样检查私有性。
请注意,use
只为 use
出现的特定作用域创建快捷方式。示例 7-12 将 eat_at_restaurant
函数移动到一个名为 customer
的新子模块中,这与 use
语句的作用域不同,因此函数体将无法编译。
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();
}
}
use
语句仅在其所在的作用域中适用编译器错误显示快捷方式不再在 customer
模块中适用
$ cargo build
Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0433]: failed to resolve: use of undeclared crate or module `hosting`
--> src/lib.rs:11:9
|
11 | hosting::add_to_waitlist();
| ^^^^^^^ use of undeclared crate or module `hosting`
|
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
引用父模块中的快捷方式。
创建符合语言习惯的 use
路径
在示例 7-11 中,您可能想知道为什么我们指定 use crate::front_of_house::hosting
,然后在 eat_at_restaurant
中调用 hosting::add_to_waitlist
,而不是像示例 7-13 中那样,将 use
路径一直指定到 add_to_waitlist
函数以达到相同的结果。
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();
}
use
将 add_to_waitlist
函数引入作用域,这不符合语言习惯尽管示例 7-11 和示例 7-13 都完成了相同的任务,但示例 7-11 是使用 use
将函数引入作用域的符合语言习惯的方式。使用 use
将函数的父模块引入作用域意味着我们在调用函数时必须指定父模块。在调用函数时指定父模块可以清楚地表明该函数不是本地定义的,同时最大限度地减少了完整路径的重复。示例 7-13 中的代码不清楚 add_to_waitlist
在哪里定义。
另一方面,当使用 use
引入结构体、枚举和其他项时,符合语言习惯的做法是指定完整路径。示例 7-14 显示了将标准库的 HashMap
结构体引入二进制 crate 作用域的符合语言习惯的方式。
use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
map.insert(1, 2);
}
HashMap
引入作用域这种习惯用法背后没有强有力的理由:这只是已经出现的约定,人们已经习惯了以这种方式阅读和编写 Rust 代码。
此习惯用法的例外情况是,如果我们使用 use
语句将两个同名的项引入作用域,因为 Rust 不允许这样做。示例 7-15 显示了如何将两个同名但父模块不同的 Result
类型引入作用域,以及如何引用它们。
如您所见,使用父模块区分了两个 Result
类型。相反,如果我们指定 use std::fmt::Result
和 use std::io::Result
,我们将在同一作用域中拥有两个 Result
类型,并且当我们使用 Result
时,Rust 将不知道我们指的是哪一个。
使用 as
关键字提供新名称
对于使用 use
将两个同名类型引入同一作用域的问题,还有另一种解决方案:在路径之后,我们可以为该类型指定 as
和一个新的本地名称或别名。示例 7-16 显示了通过使用 as
重命名两个 Result
类型之一来编写示例 7-15 中代码的另一种方法。
as
关键字引入作用域时,重命名类型在第二个 use
语句中,我们为 std::io::Result
类型选择了新名称 IoResult
,这不会与我们也引入作用域的 std::fmt
中的 Result
冲突。示例 7-15 和示例 7-16 被认为是符合语言习惯的,因此选择取决于您!
使用 pub use
重新导出名称
当我们使用 use
关键字将名称引入作用域时,新作用域中可用的名称是私有的。要使调用我们代码的代码能够引用该名称,就好像它已在该代码的作用域中定义一样,我们可以组合 pub
和 use
。此技术称为重新导出,因为我们将一个项引入作用域,同时也使该项可供其他人引入到他们的作用域中。
示例 7-17 显示了示例 7-11 中的代码,其中根模块中的 use
已更改为 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();
}
pub use
使名称可供任何代码从新作用域中使用在此更改之前,外部代码必须通过使用路径 restaurant::front_of_house::hosting::add_to_waitlist()
来调用 add_to_waitlist
函数,这也将需要将 front_of_house
模块标记为 pub
。现在,此 pub use
已从根模块重新导出 hosting
模块,外部代码可以使用路径 restaurant::hosting::add_to_waitlist()
代替。
当您的代码的内部结构与调用您的代码的程序员对域的看法不同时,重新导出非常有用。例如,在这个餐厅的比喻中,经营餐厅的人会考虑“前厅”和“后厨”。但是,光顾餐厅的顾客可能不会用这些术语来考虑餐厅的各个部分。使用 pub use
,我们可以使用一种结构编写代码,但公开另一种结构。这样做使我们的库对于在库上工作的程序员和调用库的程序员都组织良好。我们将在 “使用 pub use
导出便捷的公共 API” 中查看 pub use
的另一个示例以及它如何影响您的 crate 的文档。第 14 章的章节。
使用外部包
在第 2 章中,我们编写了一个猜谜游戏项目,该项目使用一个名为 rand
的外部包来获取随机数。要在我们的项目中使用 rand
,我们在 Cargo.toml 中添加了这一行
rand = "0.8.5"
在 Cargo.toml 中添加 rand
作为依赖项会告诉 Cargo 从 crates.io 下载 rand
包及其任何依赖项,并使 rand
可用于我们的项目。
然后,为了将 rand
定义引入我们包的作用域,我们添加了一个 use
行,该行以 crate 的名称 rand
开头,并列出了我们要引入作用域的项。回想一下,在 “生成随机数” 中第 2 章的章节中,我们将 Rng
trait 引入作用域并调用了 rand::thread_rng
函数
Rust 社区的成员在 crates.io 上提供了许多包,将其中任何一个拉取到您的包中都涉及相同的步骤:在您包的 Cargo.toml 文件中列出它们,并使用 use
将项从它们的 crate 引入作用域。
请注意,标准 std
库也是一个外部于我们包的 crate。由于标准库随 Rust 语言一起提供,因此我们不需要更改 Cargo.toml 以包含 std
。但是,我们确实需要使用 use
来引用它,以便将那里的项引入我们包的作用域。例如,对于 HashMap
,我们将使用这一行
这是一个以 std
开头的绝对路径,std
是标准库 crate 的名称。
使用嵌套路径清理大型 use
列表
如果我们使用在同一 crate 或同一模块中定义的多个项,则在单独的行中列出每个项可能会占用我们文件中的大量垂直空间。例如,我们在示例 2-4 的猜谜游戏中使用的这两个 use
语句将 std
中的项引入作用域
相反,我们可以使用嵌套路径在一行中将相同的项引入作用域。我们通过指定路径的公共部分,后跟两个冒号,然后在花括号中包含路径的不同部分列表来完成此操作,如示例 7-18 所示。
在更大的程序中,使用嵌套路径从同一 crate 或模块中将许多项引入作用域可以大大减少所需的单独 use
语句的数量!
我们可以在路径的任何级别使用嵌套路径,这在组合共享子路径的两个 use
语句时非常有用。例如,示例 7-19 显示了两个 use
语句:一个将 std::io
引入作用域,另一个将 std::io::Write
引入作用域。
use std::io;
use std::io::Write;
use
语句,其中一个语句是另一个语句的子路径这两个路径的公共部分是 std::io
,这是完整的第一个路径。要将这两个路径合并为一个 use
语句,我们可以在嵌套路径中使用 self
,如示例 7-20 所示。
use std::io::{self, Write};
use
语句此行将 std::io
和 std::io::Write
引入作用域。
Glob 运算符
如果我们想将路径中定义的所有公共项都引入作用域,我们可以指定该路径,后跟 *
glob 运算符
此 use
语句将 std::collections
中定义的所有公共项都引入当前作用域。使用 glob 运算符时要小心!Glob 可能会使您更难分辨哪些名称在作用域中以及程序中使用的名称在哪里定义。
glob 运算符通常在测试时使用,以将所有被测内容都引入 tests
模块;我们将在 “如何编写测试” 中讨论这一点第 11 章的章节。glob 运算符有时也用作 prelude 模式的一部分:请参阅 标准库文档以获取有关该模式的更多信息。