当前位置: 首页 > ds >正文

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::Resultuse 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::iostd.io.Write 带进当前作用域。

通配符操作符(Glob Operator)

若想把某一路径下所有公开定义都带进当前作用域,可以加上 * 通配符操作符,例如:

use std::collections::*;

这会把 std.collections 下所有公开元素全部导入当前范围。

请谨慎使用通配符!通配符可能让你难以判断哪些名称处于可见状态,以及程序里某名称具体来自哪里。

通配符通常用于测试时,把被测代码全部放进 tests 模块;第十一章“如何编写测试”会详细讲解。此外,它也偶尔作为预置模式(prelude pattern)的一部分出现——有关该模式更多信息,请参阅标准库文档。

http://www.xdnf.cn/news/16590.html

相关文章:

  • python中的容器与自定义容器
  • SpringBoot多容器化实例实战
  • FFmpeg——参数详解
  • 墨者:通过手工解决SQL手工注入漏洞测试(MongoDB数据库)
  • C++学习(线程相关)
  • 负载均衡Haproxy
  • SABR-Net
  • uniapp input 聚焦时键盘弹起滚动到对应的部分
  • iOS安全和逆向系列教程 第21篇:iOS应用加密与混淆技术深度剖析
  • Java面试宝典:MySQL性能优化
  • 用 ESP32 和 LCD 轻松显示植物湿度
  • 第十八章:AI的“通感”:揭秘图、文、音的共同语言——CLIP模型
  • 系统整理Python的循环语句和常用方法
  • Keil MDK 嵌入式开发问题:Error: L6218E: Undefined symbol HAL_TIM_PWM_ConfigChannel
  • GIt学习——分布式版本控制工具
  • 设计模式(八)结构型:桥接模式详解
  • 设计模式(七)结构型:适配器模式详解
  • 【网络协议安全】任务15:DHCP与FTP服务全配置
  • 安装Selenium⾃动化
  • PiscCode使用OpenCV实现漂浮方块特效
  • 三种常用的抗锯齿
  • Java大数据面试实战:Hadoop生态与分布式计算
  • esp32s3创建rust工程 window成功mac
  • 结构化文本文档的内容抽取与版本重构策略
  • net8.0一键创建支持(Orm-Sqlite-MySql-SqlServer)
  • 【最新最完整】SpringAI-1.0.0开发MCP Server,搭建MCP Client 实战笔记(进阶+详细+完整代码)
  • Map(HashMap、LinkedHashMap、TreeMap)双列集合
  • 【机器学习深度学习】LLaMAFactory评估数据与评估参数解析
  • 《频率之光:危机降临》
  • 下载 | Win11 官方精简版,系统占用空间极少!(7月更新、Win 11 IoT物联网 LTSC版、适合老电脑安装使用)