Refined 'src/packages_crates_and_modules/the_use_keyword.md'.

This commit is contained in:
Hector PENG 2024-10-07 20:41:10 +08:00
parent 4750e1292d
commit 7fcba013b0
4 changed files with 67 additions and 55 deletions

View File

@ -0,0 +1,6 @@
[package]
name = "idomatic_use"
version = "0.1.0"
edition = "2021"
[dependencies]

View File

@ -0,0 +1,7 @@
use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
map.insert(1, 2);
println! ("{:#?}", map);
}

View File

@ -493,7 +493,7 @@ $ cargo run  ✔ 
现在我们有了用户输入和随机数,我们可以对他们进行比较。该步骤如下清单 2-4 所示。请注意,这段代码还不能编译,我们将对此进行说明。
<a name="list_2-4"></a>
文件名:`src/main.rs`
```rust

View File

@ -4,8 +4,9 @@
必须写出调用函数的路径,可能会让人感到不便和重复。在 [清单 7-7](paths.md#list_7-7) 中,无论我们选择 `add_too_waitlist` 函数的绝对路径还是相对路径,每次要调用 `add_too_waitlist` 时,都必须指定 `front_of_house``hosting`。幸运的是,有种方法可以简化这一过程:我们可以使用 `use` 关键字,为路径创建一个快捷方式,然后在作用域的其他地方,使用这个较短的名字。
下面清单 7-11 中,就将 `crate::front_of_house::hosting` 模组,带入到了 `eat_at_restaurant` 函数的作用域,由此就只须指明 `hosting::add_to_wait`,而在 `eat_at_restaurant` 中调用这个 `add_to_waitlist` 函数了
清单 7-11 中,我们将 `crate::front_of_house::hosting` 模组,引入了 `eat_at_restaurant` 函数的作用域,因此只需指明 `hosting::add_too_waitlist`,就能调用 `eat_at_restaurant` 中的 `add_too_waitlist` 函数
<a name="list_7-11"></a>
文件名:`src/lib.rs`
```rust
@ -22,11 +23,12 @@ pub fn eat_at_restaurant() {
}
```
*清单 7-11使用 `use` 关键字,将模组带入到作用域*
*清单 7-11使用 `use` 关键字,将模组纳入作用域*
在作用域中添加 `use` 及某个路径,与在文件系统中创建一个符号链接类似。通过在该代码箱根处,添加 `use crate::front_of_house::hosting`,那么 `hosting` 现在就是一个有效的名字,就如同这个 `hosting` 模组,已在该代码箱根中被定义过一样。使用 `use` 关键字带入到作用域中的那些路径,与任何其他路径一样,同样会检查隐私性。
在某个作用域中加入 `use` 关键字及某个路径,类似于在文件系统中创建一个符号链接。而通过在代码箱根中添加 `use crate::front_of_house::hosting``hosting` 现在便是该作用域中的一个有效名字,就像在代码箱根中已经定义了 `hosting` 模组一样。使用 `use` 关键字纳入作用域的路径,也会像其他路径一样检查隐私。
请注意,`use` 关键字只会为该 `use` 语句所在的特定作用域,创建快捷方式。清单 7-12 将 `eat_at_restaurant` 函数,移到了一个名为 `customer` 的新子模组中,该子模组的作用域与 `use` 语句的作用域不同,因此这个函数体就无法编译。
请注意 `use` 关键字只会针对在该 `use` 出现的特定作用域,创建快捷方式。下面清单 7-12 将 `eat_at_restaurant` 移入到了新的名为 `customer` 的子模组中,这个模组就与那个 `use` 语句属于不同作用域了,因此那个函数体就不会编译:
文件名:`src/lib.rs`
@ -46,9 +48,9 @@ mod customer {
}
```
*清单 7-12`use` 语句只适用于所在的作用域*
*清单 7-12`use` 语句只适用于所在的作用域*
编译器错误指出,在 `customer` 模组里头,那个快捷方式不再适用
编译器错误显示,快捷方式不再适用于这个 `customer` 模组
```console
$ cargo build
@ -72,15 +74,14 @@ warning: `restaurant` (lib) generated 1 warning
error: could not compile `restaurant` due to previous error; 1 warning emitted
```
请注意这里还有那个 `use` 在其作用域中已不再被使用的一个告警!为修复此问题,就同时要将那个 `use` 语句,移入到那个 `customer` 模组内部,或者在那个子 `customer` 模组内部,以 `super::hosting`引用父模组中的那个快捷方式。
请注意这里还有个警告,即在其作用域中那个 `use` 不再被用到!要解决这个问题,同时要把这个 `use` 语句,移到 `customer` 模组中,或者在 `customer` 子模组中,使用 `super::hosting` 引用父模组中的那个快捷方式。
## 创建惯用 `use` 路径
**Creating Idiomatic `use` Paths**
在上面的清单 7-11 中,你或许会想,为什么那里指定了 `use crate::front_of_house::hosting`,并随后在 `eat_at_restaurant` 函数中调用了 `hosting::add_to_waitlist`,而不是将那个 `use` 路径,指定为一直到那个 `add_to_waitlist` 函数,以达到同样目的,即如下清单 7-13 中那样。
在 [清单 7-11](#list_7-11) 中,咱们可能想知道,为什么我们指定了使用 `crate::front_of_house::hosting`,然后在 `eat_at_restaurant` 中调用 `hosting::add_to_waitlist`,而不是像清单 7-13 中那样,为了达到同样的效果,指定出直达 `add_too_waitlist` 函数的 `use` 路径。
文件名:`src/lib.rs`
@ -98,13 +99,13 @@ pub fn eat_at_restaurant() {
}
```
*清单 7-13使用 `use``add_to_waitlist` 带入到作用域,此为非惯用做法*
*清单 7-13使用 `use``add_to_waitlist` 纳入作用域,这种非惯常用法*
尽管清单 7-11 与 7-13 都完成了同样任务,但清单 7-11 则是以 `use` 关键字将函数带入到作用域的惯用方式。以 `use` 关键字将函数的父模组带入到作用域中,就意味着在调用该函数时,必须指明父模组。而在调用函数时指明父模组,就令到该函数是非本地函数,这一事实变得明了,同时仍旧减少了完整路径的重复。而清单 7-13 中的代码,对于 `add_to_waitlist` 在何处创建,则并不清楚
尽管清单 7-11 和清单 7-13 都完成了同样任务,但清单 7-11 是将函数纳入作用域的惯用方法。使用 `use` 将函数的父模组纳入作用域,意味着我们必须在调用该函数时,指明其父模组。在调用函数时指定父模组,可以清楚地表明该函数不是本地定义的,同时仍然最大限度减少了完整路径的重复。清单 7-13 中的代码并不明晰,因为`add_too_waitlist` 的定义位置不明晰
另一方面,在使用 `use` 关键字,将结构体、枚举及其他程序项目带入时,惯用的就是指明完整路径了。下面清单 7-14 给出了将标准库的 `HashMap`,带入到某个二进制代码箱的惯用方式
而另一方面,在以 `use` 关键字引入结构体、枚举及其他一些项目时,指定完整路径就是种习惯做法。清单 7-14 展示了将标准库的 `HashMap` 结构体,纳入某个二进制代码箱作用域的惯用方法
文件名:`src/lib.rs`
文件名:`src/main.rs`
```rust
use std::collections::HashMap;
@ -115,11 +116,11 @@ fn main() {
}
```
*清单 7-14以惯用方式将 `HashMap` 带入到作用域*
*清单 7-14以惯用方式将 `HashMap` 纳入作用域*
种惯用语法背后并没有什么有力理由:他不过是业已形成的约定,且人们已经习惯了以这样的方式,阅读和编写 Rust 代码。
个习惯用法背后,并没有很强的理由:他只是种约定俗成的习惯,人们已经习惯了用这种方式,来阅读和编写 Rust 代码。
由于 Rust 不允许使用 `use` ,将两个有着同样名字的程序项目带入到作用域,那么这就正是此惯用语法的例外了。下面清单 7-15 给出了,怎样将两个有着同样名字,但父模组不同的 `Result` 类型带入作用域,及怎样去引用他们。
如果我们要使用 `use` 语句,将两个同名项目纳入作用域,那么这个习惯用法就是个例外,因为 Rust 不允许这样做。清单 7-15 展示了如何将两个名字相同但父模组不同的 `Result` 类型纳入作用域,以及如何引用他们。
文件名:`src/lib.rs`
@ -136,17 +137,16 @@ fn function2() -> io::Result {
}
```
*清单 7-15有着同样名字的两种类型带入到同一作用域,就要求使用他们的父模组*
*清单 7-15两个同名类型纳入同一作用域,就需要用到他们的父模组*
可以看到,这里使用父模组,就将两个 `Result` 类型区分开了。相反如果指明的是 `use std::fmt::Result;``use std::io::Result;`,就会得到同一作用域中的两个 `Result` 类型,而 Rust 就不明白在使用 `Result` 时,到底是要哪个了
正如咱们所见到的,使用父模组就可以区分这两种 `Result` 类型。而如果我们指定了 `use std::fmt::Result``use std::io::Result`,我们就会在同一作用域中,有了两个 `Result` 类型,但 Rust 不会知道,我们使用 `Result` 时指的是哪个
## 使用 `as` 关键字提供新名字
**Providing New Names with the `as` Keyword**
解决以 `use` 关键字将有着同样名字的两个类型,带入到同一作用域的问题,还有另一方法:在路径后面,可指定 `as`,与该类型的一个新本地名字,或者说 *别名alias*。下面清单 7-16 给出了通过将那两个 `Result` 类型中的一个,使用 `as` 关键字进行重命名,而编写清单 7-15 中代码的另一种方式。
还有另一种方法,可以解决将两个同名类型纳入同一作用域的问题:在路径之后,我们可以指定 `as` 和一个新的本地名字,或 *别名alias*。清单 7-16 展示了另一种编写清单 7-15 代码的方法,即使用 `as` 重命名两个 `Result` 类型中的一个。
文件名:`src/lib.rs`
@ -163,20 +163,18 @@ fn function2() -> IoResult {
}
```
*清单 7-16在将某个类型带入作用域时使用 `as` 关键字对其进行重命名*
*清单 7-16将某个类型纳入作用域时使用 `as` 关键字对其重命名*
在第二个 `use` 语句中,我们为 `std::io::Result` 类型,选择了新名字 `IoResult`,这不会与我们同时引入该作用域的 `std::fmt` 中的 `Result` 冲突。清单 7-15 和清单 7-16 都被认为是惯用的,因此选择由咱们自己决定!
在第二个 `use` 语句中,选择了 `IoResult` 作为 `std::io::Result` 类型的新名字,这就不会与同时带入到作用域的、来自 `std::fmt``Result` 冲突了。清单 7-15 与清单 7-16 都被当作惯用方式,因此选择哪个就随你所愿了!
## 使用 `pub use` 将名字重新导出
## 使用 `pub use` 重新导出名字
**Re-exporting Names with `pub use`**
当我们使用 `use` 关键字将名字纳入作用域时,新作用域中可用的这个名字是私有的。为了使调用咱们代码的代码,就好像这个名字是在咱们代码作用域中定义的那样,引用这个名字,我们可以将 `pub``use` 结合使用。这种技术被称为 *再导出re-exporting*,因为我们在将某个项目纳入作用域的同时,还让其他人可以将该项目纳入他们的作用域了。
在使用 `use` 关键字将某个名字带入到作用域中时,这个在新作用域中可用的名字即为私有的。为了那些会调用到引入作用域代码的其他代码,能够像这个名字是被定义在引入到作用域的代码的作用域中一样,对这个名字进行引用,这时就可以结合上 `pub``use` 关键字。由于这里是将某个程序项目带入到作用域,而又同时将那个程序项目构造为可被其他代码将其带入他们的作用域,因此该技巧被称为 *重导出re-exporting*
下面清单 7-17 给出了将根模组中的 `use` 修改为 `pub use` 后,清单 7-11 中的代码。
清单 7-17 展示清单 7-11 中的代码,其中根模组中的 `use` 已更改为 `pub use`
文件名:`src/lib.rs`
@ -194,29 +192,28 @@ pub fn eat_at_restaurant() {
}
```
*清单 7-17使用 `pub use`,于一个新作用域处将某个名字构造为对任意代码可用*
*清单 7-17使用 `pub use` 让任何代码都可以从新作用域中使用某个名字*
此项修改之前,外部代码必须通过使用路径 `restaurant::front_of_house::hosting::add_to_waitlist()`,来调用其中的 `add_to_waitlist` 函数。现在既然这个 `pub use` 已将该 `hosting` 模组,自根模组中重新导出,那么外部代码现在就可以使用 `restaurant::hosting::add_to_waitlist()` 路径了。
这一修改之前,外部代码必须使用 `restaurant::front_of_house::hosting::add_too_waitlist()` 路径,来调用 `add_to_waitlist` 函数,这还需要 `front_of_house` 模组被标记为 `pub`。现在,这个 `pub use` 已经从该根模组中,重新导出了 `hosting` 模组,外部代码就可以使用 `restaurant::hosting::add_to_waitlist()` 路径了。
在所编写代码的内部结构,与调用代码的程序员们对该领域有着不同设想时,重导出就是有用的。比如,在这个饭馆的比喻中,运营该饭馆的人设想的是“前厅”与“后厨”。但造访饭馆的食客,或许不会用这样的词汇,来认识饭馆的这些部位。有了 `pub use`,就可以一种结构来编写代码,而以另一种结构将代码暴露出来。这样做就让这个库,对于在该库上编写代码的程序员,与调用这个库的程序员,均具备良好的组织。在第 14 章的 [“运用 `pub use` 导出便利的公共 API”](Ch14_More_about_Cargo_and_Crates_io.md#使用-pub-use-导出好用的公开-api) 小节,将会看到另一个 `pub use` 的示例,并了解他是怎样影响到代码箱的文档
当咱们代码的内部结构,与调用咱们代码的程序员对有关领域的理解有差别时,重新导出就非常有用。例如,在这个餐馆的比方中,经营餐馆的人考虑的是“前厅”和“后厨”。但光顾餐厅的顾客,可能不会从这些角度来考虑餐厅的各个部分。通过 `pub use`,我们可以一种结构编写代码,而暴露出不同的结构。这样做可以使我们的程序库井井有条,便于编写这个库的程序员,也便于调用这个库的程序员。我们将在第 14 章的 [“使用 `pub use` 导出好用的公开 API”](../crates-io/publishing.md##使用-pub-use-导出便利的公开-api) 小节,介绍 `pub use` 的另一个示例,以及他对代码箱文档的影响
## 使用外部 Rust
## 使用外部包
**Using External Packages**
在第 2 章中,那里曾编写了用到名为 `rand` 外部包来获取一个随机数的猜数游戏项目。为了在项目中使用 `rand`,那里曾添加下面这行到 `Cargo.toml` 文件:
在第 2 章中,我们编写了个竞猜游戏项目,该项目使用了名为 `rand` 的外部包,来获取随机数。为了在项目中使用 `rand`,我们在 `Cargo.toml` 中添加了这一行:
文件名:`Cargo.toml`
```toml
rand = `0.8.3`
rand = `0.8.5`
```
`rand` 作为依赖项添加到 `Cargo.toml`,就告诉 Cargo去 [crates.io](https://crates.io/) 下载那个 `rand` 包和任何的依赖项,而令到 `rand` 对此项目可用
`Cargo.toml` 中将 `rand` 添加为依赖项后Cargo 就会从 [crates.io](https://crates.io/) 下载 `rand` 包和任何依赖项,并将 `rand` 提供到咱们的项目
随后为了将 `rand` 的一些定义,带入到所编写的包中,这里添加了以代码箱名字,`rand`,开头,并列出了打算要带入到作用域中的那些条目的一个 `use` 行。回顾第 2 章中的 [“生成一个随机数”](Ch02_Programming_a_Guessing_Game.md#生成随机数) 小节,那里就将那个 `Rng` 特质,带入到了作用域,并调用了 `rand::thread_rng` 函数:
然后,为了将 `rand` 定义带入咱们包的作用域,我们添加了以该代码箱名字 `rand` 开头的 `use` 行,并列出了咱们打算带入作用域的那些项目。清回想一下,在第 2 章 [“生成随机数”](../Ch02_Programming_a_Guessing_Game.md#生成随机数) 小节中,我们将 `Rng` 特质引入了作用域,并调用了 `rand::thread_rng` 函数:
```rust
use rand::Rng;
@ -226,23 +223,23 @@ fn main() {
}
```
Rust 社群业已构造了可在 [crates.io](https://crates.io/) 上取得的许多 Rust 包,而将任意的这些包,拉取进入自己的包,都涉及到这些同样步骤:将他们列在自己包的 `Cargo.toml` 文件中,并使用 `use` 来将他们代码箱中的条目,带入到作用域中
Rust 社区成员在 [crates.io](https://crates.io/) 上,已经提供了许多包,将其中任何包拉入咱们的包,都需要这些同样步骤:在咱们包的 `Cargo.toml` 文件中列出他们,并使用 `use` 将他们代码箱中的项目,引入到作用域
请注意标准库(`std`)同样是个相对本地包的外部代码箱。由于标准库是 Rust 语言本身附带的,因此就无需修改 `Cargo.toml` 文件为包含 `std`。但为了将 `std` 中的条目带入到本地包作用域,是需要以 `use` 来引用他。比如,以 `HashMap` 来说,就要使用下面这行:
请注意,标准库 `std` 同样属于相对于咱们包外部的一个代码箱。因为标准库是随 Rust 语言一起提供的,所以我们不需要修改 `Cargo.toml` 来包含 `std`。但我们确实需要用 `use` 来引用他,以便将其中的项目引入咱们包的作用域。例如,对于 `HashMap` 我们可以使用这行:
```rust
use std::collections::HashMap;
```
这是一个以 `std`,即标准库代码箱名字,开头的绝对路径。
这是个以 `std`(标准库代码箱名字)开头的绝对路径。
## 运用嵌套路径,清理大型 `use` 清单
## 使用嵌套路径清理大型 `use` 清单
**Using Nested Paths to Clean Up Large `use` Lists**
在用到定义在同一代码箱或同一模组中的多个条目时,若各自行上地列出这些条目,那么就会占据文件中的很多纵向空间。比如,清单 2-4 中的猜数游戏里,就有下面这两个 `use` 语句,他们将 `std` 中的两个条目带入到作用域:
如果我们使用的是定义在同一代码箱,或同一模组中的多个项目,而将每个项目单独列一行,就会占用文件中的大量垂直空间。例如,我们在 [清单 2-4](Ch02_Programming_a_Guessing_Game.md#list_2-4) 的猜谜游戏中,使用的这两条 `use` 语句,将 `std` 中的项目引入了作用域:
文件名:`src/main.rs`
@ -253,7 +250,7 @@ use std::io;
// --跳过--
```
相反,这里就可以使用嵌套路径,来在一个行中,把来自同一代码箱或包的那些条目,带入到作用域。通过指明路径的共同部分,接上一对冒号,及随后的花括号封闭包围起来的那些路径各异部分的清单,就完成了这一点,如下代码清单 7-18 所示。
实际上,我们可以使用嵌套路径,在一行中将同样这些项目纳入作用域。具体做法是指定出路径的共同部分,后面加两个冒号,然后在这些项目的路径不同部分列表周围,加上花括号,如清单 7-18 所示。
文件名:`src/main.rs`
@ -263,11 +260,12 @@ use std::{cmp::Ordering, io};
// --跳过--
```
*清单 7-18指定出嵌套路径,来将多个有着同样前缀的程序项目带入到作用域*
*清单 7-18指定嵌套路径,将具有同样前缀的多个项目纳入作用域*
在更为大型的程序中,使用嵌套路径,将许多的程序项目,从同一代码箱或模组带入到作用域,可极大地减少所需的单独 `use` 语句数目!
在大型程序中,使用嵌套路径将同一代码箱或同一模组中的许多项目引入作用域,可以大大减少所需的单独 `use` 语句的数量!
在路径的任何层级,我们都可以使用嵌套路径,在对共用子路径的两个 `use` 语句加以组合时,这尤为有用。例如,清单 7-19 给出了两个 `use` 语句:一个将 `std::io` 引入作用域,另一个将 `std::io::Write` 引入作用域。
在路径中的任何级别,都可使用嵌套路径,在对两个共用了子路径的 `use` 语句进行组合时,这是有用的。比如下面清单 7-19 就给出了两个 `use` 语句:一个将 `std::io` 带入到作用域,而另一个则是将 `std::io::Write` 带入到作用域。
文件名:`src/lib.rs`
@ -276,9 +274,10 @@ use std::io;
use std::io::Write;
```
*清单 7-19其中一个为另一个子路径的两个 `use` 语句*
*清单 7-19两个 `use` 语句,其中一个是另一个的子路径*
这两个路径的共同部分是 `std::io`,正是完整的第一个路径。要将这两个路径合并为一条 `use` 语句,我们可以在嵌套路径中使用 `self`,如清单 7-20 所示。
这两个路径的共同部分,即是 `std::io`,且那就是完整的第一个路径。为将这两个路径融合为一个 `use` 语句,这里可在嵌套路径中,使用 `self` 关键字,如下清单 7-20 中所示。
文件名:`src/main.rs`
@ -286,22 +285,22 @@ use std::io::Write;
use std::io::{self, Write};
```
*清单 7-20将清单 7-19 中的两个路径合为一个 `use` 语句*
*清单 7-20将清单 7-19 中的路径合为一个 `use` 语句*
这行代码就将 `std::io``std::io::Write` 带入到了作用域。
这行代码会将 `std::io``std::io::Write` 纳入作用域。
## 全局操作符
## 全局操作符`*`
**The Glob Operator**
如果我们想将某个路径中定义的 *所有* 公开项目都纳入作用域,可以在指定路径后加上 `*` 这个全局操作符:
在打算将某个路径中的 *全部all* 公开条目,都带入到作用域时,可将那个路径,后面跟上 `*`,即全局操作符,而予以指定:
```rust
use std::collections::*;
```
`use` 语句,将定义在 `std::collections` 中的全部公开项目,都带入到了当前作用域。在使用这个全局操作符时要当心!全局带入,会导致更难于分清哪些名字是作用域中,与在所编写程序中用到的名字,是在何处定义的
`use` 语句会将 `std::collections` 中定义的所有公开项目引入当前作用域。使用全局操作符时要当心!全局性会使咱们更难分辨,哪些名称是在作用域中,以及程序中使用的名字是在何处定义
通常是在测试时,要将正在测试的全部程序项目带入到 `tests` 模组,才使用这个全局操作符;在第 11 章中的 [怎样编写测试](Ch11_Writing_Automated_Tests.md#怎样编写测试) 小节就会讲到这个问题。在序曲模式the prelude pattern有时也会用到全局操作符请参阅 [标准库文档](https://doc.rust-lang.org/std/prelude/index.html#other-preludes),了解有关更多序曲模式的知识
全局操作符通常在测试时使用,以便将所有被测试内容引入 `tests` 模块;我们将在第 11 章 [“如何编写测试”](../automated_tests/howto.md#怎样编写测试) 小节中讨论这个问题。全局操作符有时也作为前奏模式the prelude pattern的一部分使用有关该模式的更多信息请参阅 [标准库文档](https://doc.rust-lang.org/std/prelude/index.html#other-preludes)。