07.4-使用 use 关键字引入路径
使用 use 关键字引入路径
每次调用函数时都必须写出完整路径,可能会感觉不便且重复。在清单7-7中,无论我们选择绝对路径还是相对路径来调用 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 函数。
文件名:src/lib.rs
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();
}
清单7-11:使用 use 将模块引入作用域
在作用域中添加 use 和一个路径类似于在文件系统中创建符号链接。通过在 crate 根目录添加 use crate::front_of_house::hosting,hosting 就成为该作用域中的有效名称,就好像 hosting 模块定义在 crate 根目录一样。通过 use 引入的路径同样会检查隐私权限,与其他任何路径一样。
注意,use 仅为其所在的特定作用域创建快捷方式。清单7-12将 eat_at_restaurant 函数移动到名为 customer 的新子模块,这与 use 声明所在的作用域不同,因此函数体无法编译。
文件名:src/lib.rs
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(); }
}
清单7-12:use 语句仅适用于其所在的作用域。
编译器错误显示该快捷方式不再适用于 customer 模块:
$ cargo buildCompiling 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();| ^^^^^^^ 使用了未声明的crate或模块`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)]` 默认开启有关此错误的更多信息,请尝试运行 `rustc --explain E0433`。
warning: `restaurant` (lib) generated 1 warning
error: could not compile `restaurant` (lib) due to 1 previous error; 1 warning emitted
请注意,还有一条警告提示该 scope 内已不再使用该 import!要解决此问题,可以将 use 移动到 customer 模块内部,或者在子模块 customer 中通过 super::housing 引用父模块中的快捷方式。
创建惯用的 use 路径
在清单 7-11 中,你可能会想,为什么我们指定了 use crate::front_of_house::hosting
,然后在 eat_at_restaurant
中调用 hosting::add_to_waitlist
,而不是像清单 7-13 那样直接指定到函数 add_to_waitlist
的完整路径来达到同样的效果。
文件名:src/lib.rs
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-13:使用 use 将函数 add_to_waitlist 引入作用域,这种写法不够惯用
虽然清单 7-11 和 7-13 都完成了相同的任务,但清单 7-11 是将函数通过 use 引入作用域的惯用方式。通过 use 引入父模块意味着调用该函数时必须指定父模块。这种调用方式明确表明该函数不是本地定义,同时又最大限度减少了完整路径的重复。相比之下,清单 7-13 的代码不太明确 add_to_waitlist 函数定义的位置。
另一方面,当引入结构体、枚举和其他项时,通过 use 指定完整路径是惯用做法。清单 7-14 展示了如何以惯用方式将标准库中的 HashMap 结构体引入二进制包(binary crate)的作用域。
文件名:src/main.rs
use std::collections::HashMap;fn main() {let mut map = HashMap::new();map.insert(1, 2);
}
清单 7-14:以惯用方式将 HashMap 引入作用域
这种习惯没有特别强烈的理由,只是约定俗成,人们已经习惯这样读写 Rust 代码。
这个习惯唯一例外的是当我们需要通过多个 use 声明把两个同名项引入作用域时,因为 Rust 不允许这样做。清单 7-15 展示了如何把两个不同父模块但名称相同的 Result 类型引入,并且如何引用它们。
文件名:src/lib.rs
use std::fmt;
use std::io;fn function1() -> fmt::Result {// --省略--
}fn function2() -> io::Result<()> {// --省略--
}
清单 7-15:将两个同名类型带有其父模块一起引入相同作用域中
如你所见,通过使用父模块区分这两种 Result 类型。如果改为分别写成 use std::fmt::Result
和 use std::io::Result
,那么就会出现两个 Result 在相同作用域内冲突的问题,Rust 无法判断你指的是哪一个 Result。
使用 as 提供新名字
解决上述问题还有另一种方法,即在路径后面加上 as 并给类型起一个新的局部名字或别名。清单 7-16 演示了如何利用 as 重命名其中一个 Result 类型,从而实现与前面的代码等效功能。
文件名:src/lib.rs
use std::.fmt:Result;
use std::.io:Result as IoResult;fn function1() -> Result {// --省略--
}fn function2() -> IoResult<()> {// --省略--
}
清单 7–16: 使用 as 关键字重命名单个导入类型
第二条 use 声明中,我们选择给来自 std.io.Result
起新名字为 IoResult,这样不会与从 std.fmt.Result
导进来的 Result 冲突。无论是采用类似于 清单 7–15 或 清单 7–16 的写法,都被认为是符合规范(idiomatic)的,用哪个由你决定!
使用 pub use 重导出名称
当我们通过 use 把某个名称带进当前作用域时,该名称对外仍然是私有的。如果希望让外部代码也能像是在当前范围内一样访问该名称,可以结合 pub 和 use 一起使用。这称作“重导出”,即既把项目带进当前范围,也使得别人可以从这里再导出去使用它。
下面看一下修改后的版本,将根模块中的普通 use 改成 pub use:
文件名:src/lib.rs
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 ();
}
清單7-17 : 用 pub use 讓名稱對任何代碼都可見並可從新範圍訪問
更改之前,如果外部代码要调用这个函数,需要走全路径,比如:
restaurant :: front_of_house :: hosting :: add_to _waitlist()
同时还要求 front_of_house 模块标记为 pub。
现在由于根模块里用了 pub use 来重新导出了 hosting 模块,
所以外部只需 restaurant :: hosting :: add_ to _ wait list ()
即可访问此功能。
重导出非常适合内部代码结构和用户视角不同的时候。例如餐厅比喻中,
餐厅管理者考虑“前台”和“后台”;
但是顾客通常不会这么思考餐厅各部分。
借助 pub use ,我们可以内部按一种结构组织代码,而向用户暴露另一套更合理、更易理解的接口。
这让库开发者和库用户都能受益。
关于 pub use 及其对文档影响,我们将在第十四章《Exporting a Convenient Public API with pub use》
中详细介绍。
在第二章中使用外部包
我们编写了一个猜数字游戏项目,使用了一个名为 rand 的外部包来获取随机数。为了在项目中使用 rand,我们在 Cargo.toml 文件中添加了这一行:
文件名:Cargo.toml
rand = “0.8.5”
在 Cargo.toml 中将 rand 添加为依赖项,会告诉 Cargo 从 crates.io 下载 rand 包及其所有依赖,并使 rand 可用于我们的项目。
然后,为了将 rand 的定义引入到我们包的作用域内,我们添加了一条以 crate 名称 rand 开头的 use 语句,并列出了想要引入作用域的项。回想一下第二章“生成随机数”部分,我们将 Rng trait 引入作用域并调用了函数 rand::thread_rng:
use rand::Rng;fn main() {let secret_number = rand::thread_rng().gen_range(1..=100);
}
Rust 社区成员已经在 crates.io 上发布了许多包,将它们引入你的包也遵循相同步骤:先在你的包的 Cargo.toml 文件中列出它们,然后用 use 将这些 crate 中的项带入作用域。
注意,标准库 std 也是一个对我们包来说是外部的 crate。因为标准库随 Rust 语言一起发布,所以不需要修改 Cargo.toml 来包含 std。但我们仍然需要用 use 来引用它,从而把其中的项带进我们的代码作用域。例如,对于 HashMap,可以这样写:
use std::collections::HashMap;
这是一个以 std(标准库 crate 名称)开头的绝对路径。
使用嵌套路径简化大量 use 列表
如果从同一 crate 或模块中使用多个定义,逐条列出每个 item 会占用很多垂直空间。例如,在猜数字游戏中的 Listing 2-4 有两条从 std 引入内容的 use 声明:
文件名:src/main.rs
// --snip--
use std::cmp::Ordering;
use std::io;
// --snip--
相反,我们可以用嵌套路径一次性引入这些内容,只需指定公共部分路径后跟两个冒号,再用大括号括起不同部分,如 Listing 7-18 所示:
文件名:src/main.rs
// --snip--
use std::{cmp::Ordering, io};
// --snip--
Listing 7-18: 用嵌套路径一次性引入多个具有相同前缀的项
对于大型程序,从同一 crate 或模块通过嵌套路径导入大量项能显著减少单独 use 声明数量!
我们可以在任意层级使用嵌套路径,这对于合并共享子路径但又有差异部分的两条 use 非常有用。例如,Listing 7-19 展示两条声明,一条导入 std::io
,另一条导入 std::io::Write
:
文件名:src/lib.rs
use std::io;
use std::io::Write;
Listing 7-19: 两个互为子路径关系的 use 声明
这两者共有公共部分是 std::io
,且第一行就是完整该公共部分。要合并成一行,可利用 nested path 中 self,如 Listing 7-20 所示:
文件名:src/lib.rs
use std::io::{self, Write};
Listing 7-20: 合并 Listing 7-19 路径的一行声明
此语句同时将 std::io
和 std.io.Write
带进当前作用域。
通配符操作符(Glob Operator)
若想把某一路径下所有公开定义都带进当前作用域,可以加上 * 通配符操作符,例如:
use std::collections::*;
这会把 std.collections
下所有公开元素全部导入当前范围。
请谨慎使用通配符!通配符可能让你难以判断哪些名称处于可见状态,以及程序里某名称具体来自哪里。
通配符通常用于测试时,把被测代码全部放进 tests 模块;第十一章“如何编写测试”会详细讲解。此外,它也偶尔作为预置模式(prelude pattern)的一部分出现——有关该模式更多信息,请参阅标准库文档。