![3573e9cd8162032d62cb05d75dcdbe3b.png](https://i-blog.csdnimg.cn/blog_migrate/17bd10a2eb6b6f3a765c25e4506eb32f.jpeg)
到目前为止,似乎我们编写的用于调用函数的路径并不方便且冗长。例如,清单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在中调用add_to_waitlist函数即可 eat_at_restaurant。
文件名: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(); hosting::add_to_waitlist(); hosting::add_to_waitlist();}
清单7-11:将模块带入范围 use
use在作用域中添加和路径类似于在文件系统中创建符号链接。通过添加use crate::front_of_house::hosting板条箱根,hosting现在在该作用域中是一个有效名称,就像hosting 模块已在板条箱根中定义一样。与use 其他路径一样,进入作用域的路径也会检查隐私。
您还可以通过use和相对路径将某项纳入范围。清单7-12显示了如何指定相对路径以获得与清单7-11相同的行为。
文件名:src / lib.rs
mod front_of_house { pub mod hosting { pub fn add_to_waitlist() {} }}use self::front_of_house::hosting;pub fn eat_at_restaurant() { hosting::add_to_waitlist(); hosting::add_to_waitlist(); hosting::add_to_waitlist();}
清单7-12:通过use和相对路径将模块引入作用域
创建惯用use路径
在清单7-11中,您可能想知道为什么我们指定use crate::front_of_house::hosting然后调用hosting::add_to_waitlist, eat_at_restaurant而不是像清单7-13那样use一直指定到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(); add_to_waitlist(); add_to_waitlist();}
清单7-13:瞻add_to_waitlist功能为与范围use,这是unidiomatic
尽管清单7-11和7-13都完成了相同的任务,但是清单7-11是惯用的将函数带入作用域的方法use。将函数的父模块带入范围内,use因此我们在调用函数时必须指定父模块,这清楚地表明该函数不是本地定义的,同时仍使完整路径的重复最小化。清单7-13中的代码不清楚在哪里add_to_waitlist定义。
另一方面,当使用引入结构,枚举和其他项时use,习惯性的是指定完整路径。清单7-14显示了将标准库的HashMap结构引入二进制条板箱范围的惯用方式。
文件名: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 { // --snip--}fn function2() -> io::Result { // --snip--}
代码清单7-15:将两个具有相同名称的类型带入相同的作用域需要使用它们的父模块
如您所见,使用父模块可以区分这两种Result类型。如果相反,我们指定use std::fmt::Result和use std::io::Result,我们将Result在同一范围内有两种类型,而Rust在使用时将不知道我们指的是哪一种Result。
使用as关键字提供新名称
还有另一个解决方案,可以通过以下方式将相同名称的两种类型引入同一作用域use:在路径之后,我们可以as为该类型指定一个新的本地名称或别名。清单7-16显示了另一种通过Result使用来重命名两种类型之一来编写清单7-15中的代码的方法as。
文件名:src / lib.rs
use std::fmt::Result;use std::io::Result as IoResult;fn function1() -> Result { // --snip--}fn function2() -> IoResult { // --snip--}
清单7-16:使用as关键字将类型带入范围时重命名
在第二个use语句中,我们IoResult为 std::io::Result类型选择了新名称,这与 我们也将其纳入范围的Resultfrom不会冲突std::fmt。代码清单7-15和代码清单7-16被认为是惯用的,因此选择取决于您!
用以下方式重新导出名称 pub use
当我们使用use关键字将名称带入范围时,新范围中可用的名称是私有的。为了使调用我们代码的代码能够像在该代码范围内定义该名称一样引用该名称,我们可以将pub 和组合在一起use。这项技术称为重新导出,因为我们将某个项目放入范围内,同时也使该项目可供其他人进入其范围。
清单7-17显示了清单7-11中的代码,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(); hosting::add_to_waitlist(); hosting::add_to_waitlist();}
清单7-17:在新作用域中使名称可用于任何代码 pub use
通过使用pub use,外部代码现在可以add_to_waitlist使用调用该函数hosting::add_to_waitlist。如果未指定pub use,则该 eat_at_restaurant函数可以hosting::add_to_waitlist在其作用域内调用,但是外部代码无法利用此新路径。
当代码的内部结构与调用代码的程序员对域的思考方式不同时,重新导出很有用。例如,在这个餐厅的比喻中,经营餐厅的人会想到“房屋前部”和“房屋后部”。但是光顾这些餐厅的顾客可能不会考虑这些餐厅的组成部分。使用 pub use,我们可以使用一种结构编写代码,但可以公开不同的结构。这样做使我们的库井井有条,适合从事该库工作的程序员和调用该库的程序员。
使用外部软件包
在第2章中,我们编写了一个猜测游戏项目,该项目使用一个名为的外部软件包rand来获取随机数。要rand在我们的项目中使用,我们将此行添加到Cargo.toml中:
文件名:Cargo.toml
[dependencies]rand = "0.5.5"
rand在Cargo.toml中添加依赖项后,Cargo会rand从crates.io下载 软件包和任何依赖项,并将其rand提供给我们的项目。
然后,为了将rand定义带入包的范围,我们添加了一个use以板条箱名称开头的 行rand,并列出了我们希望带入范围的项目。回想一下,在第2章的“生成随机数”部分中,我们将Rng特征引入了范围并调用了该rand::thread_rng函数:
use rand::Rng;fn main() { let secret_number = rand::thread_rng().gen_range(1, 101);}
Rust社区的成员已经在crates.io上提供了许多软件包 ,将其中的任何一个放入您的软件包都涉及以下相同的步骤:将它们列出在软件包的Cargo.toml文件中,然后use将其包装中的物品带入范围。
请注意,标准库(std)也是我们包外部的板条箱。由于标准库随附Rust语言,因此我们不需要将Cargo.toml更改为include std。但是我们确实需要参考它,use以便将那里的物品带入我们的包装范围。例如,HashMap我们将使用以下行:
use std::collections::HashMap;
这是一个以开头的绝对路径std,即标准库箱的名称。
使用嵌套路径清理大use列表
如果我们使用在同一个板条箱或同一模块中定义的多个项目,则在每行中列出每个项目会占用我们文件中的大量垂直空间。例如,use清单2-4中的Guessing游戏中的以下两个语句将项目从std以下范围引入:
文件名:src / main.rs
// --snip--use std::cmp::Ordering;use std::io;// --snip--
相反,我们可以使用嵌套路径将同一项目放入一行中。为此,我们先指定路径的公共部分,然后是两个冒号,然后在路径不同部分的列表周围使用花括号,如清单7-18所示。
文件名:src / main.rs
// --snip--use std::{cmp::Ordering, io};// --snip--
清单7-18:指定一个嵌套路径以将具有相同前缀的多个项目带入范围
在更大的程序中,使用嵌套路径从同一板条箱或模块中将许多项目纳入范围可以减少use很多所需的独立语句!
我们可以在路径的任何级别上使用嵌套路径,这在组合两个use共享子路径的语句时非常有用。例如,清单7-19显示了两个 use语句:一个std::io进入范围,另一个 std::io::Write进入范围。
文件名:src / lib.rs
use std::io;use std::io::Write;
清单7-19:两个use语句,其中一个是另一个的子路径
这两个路径的共同部分是std::io,这就是完整的第一个路径。要将这两个路径合并为一条use语句,我们可以使用self嵌套路径,如清单7-20所示。
文件名:src / lib.rs
use std::io::{self, Write};
清单7-20:将清单7-19中的路径合并为一条use语句
此行带来了std::io和std::io::Write成的范围。
全局运算符
如果要将路径中定义的所有公共项目都纳入范围,可以指定该路径,后跟*,glob运算符:
use std::collections::*;
该use语句将定义的所有公共项目std::collections带入当前范围。使用glob运算符时要小心!Glob使得更难分辨作用域中的名称以及程序中使用的名称的定义位置。
测试时通常使用glob运算符将要测试的所有内容都放入tests模块中。我们将在第11章的“如何编写测试”部分中对此进行讨论。glob运算符有时也用作前奏模式的一部分: 有关该模式的更多信息,请参见标准库文档。