From 403cfdd1e3b1a6d74d11ebdb0fc0de6b28efd498 Mon Sep 17 00:00:00 2001 From: shengurun Date: Mon, 28 Oct 2019 14:18:32 +0800 Subject: [PATCH 1/3] =?UTF-8?q?add:=20=E6=B7=BB=E5=8A=A0=E6=96=B0=E7=89=88?= =?UTF-8?q?ch07-04=E7=9A=84=E7=BF=BB=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 部分内容参考了原版ch07-02中文版里的译法,并纠正拗口的地方。 --- ...g-paths-into-scope-with-the-use-keyword.md | 271 ++++++++++++++++++ 1 file changed, 271 insertions(+) create mode 100644 src/ch07-04-bringing-paths-into-scope-with-the-use-keyword.md diff --git a/src/ch07-04-bringing-paths-into-scope-with-the-use-keyword.md b/src/ch07-04-bringing-paths-into-scope-with-the-use-keyword.md new file mode 100644 index 0000000..4a2c6a7 --- /dev/null +++ b/src/ch07-04-bringing-paths-into-scope-with-the-use-keyword.md @@ -0,0 +1,271 @@ +## 使用 `use` 关键字将名称引入作用域 + +> [使用 `use` 关键字将名称引入作用域](https://github.com/rust-lang/book/blob/master/src/ch07-04-bringing-paths-into-scope-with-the-use-keyword.md) +>
+> commit 6d3e76820418f2d2bb203233c61d90390b5690f1 + +到目前为止,似乎我们编写的用于调用函数的路径都很冗长且重复,并不方便。例如,示例 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` 函数。 + +文件名: src/lib.rs + +```rust +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(); +} +# fn main() {} +``` + +示例 7-11: 使用 `use` 将模块引入作用域 + +在作用域中增加 `use` 和路径类似于在文件系统中创建软连接(符号连接,symbolic link)。通过在 crate 根增加 `use crate::front_of_house::hosting`,现在 `hosting` 在作用域中就是有效的名称了,如同 `hosting` 模块被定义于 crate 根一样。通过 `use` 引入作用域的路径也会检查私有性,同其它路径一样。 + +你还可以使用 `use` 和相对路径来将一个项引入作用域。示例 7-12 展示了如何指定相对路径来取得与示例 7-11 中一样的行为。 + +文件名: src/lib.rs + +```rust +mod front_of_house { + pub mod hosting { + pub fn add_to_waitlist() {} + } +} + +use front_of_house::hosting; + +pub fn eat_at_restaurant() { + hosting::add_to_waitlist(); + hosting::add_to_waitlist(); + hosting::add_to_waitlist(); +} +# fn main() {} +``` + +示例 7-12: 使用 `use` 和相对路径将模块引入作用域 + +### 创建惯用的 `use` 路径 + +在示例 7-11 中,你可能会比较疑惑,为什么我们是指定 `use crate::front_of_house::hosting` ,然后在 `eat_at_restaurant` 中调用 `hosting::add_to_waitlist` ,而不是通过指定一直到 `add_to_waitlist` 函数的 `use` 路径来得到相同的结果,如示例 7-13 所示。 + +文件名: src/lib.rs + +```rust +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(); +} +# fn main() {} +``` + +示例 7-13: 使用 `use` 将 `add_to_waitlist` 函数引入作用域,这并不符合习惯 + +虽然示例 7-11 和 7-13 都完成了相同的任务,但示例 7-11 是使用 `use` 将函数引入作用域的习惯用法。要想使用 `use` 将函数的父模块引入作用域,我们必须在调用函数时指定父模块,这样可以清晰地表明函数不是在本地定义的,同时使完整路径的重复度最小化。示例 7-13 中的代码不清楚 `add_to_waitlist` 是在哪里被定义的。 + +另一方面,使用 `use` 引入结构体、枚举和其他项时,习惯是指定它们的完整路径。示例 7-14 展示了将 `HashMap` 结构体引入二进制 crate 作用域的习惯用法。 + +文件名: src/main.rs + +```rust +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 + +```rust +use std::fmt; +use std::io; + +fn function1() -> fmt::Result { + // --snip-- +# Ok(()) +} + +fn function2() -> io::Result<()> { + // --snip-- +# Ok(()) +} +``` + +示例 7-15: 使用父模块将两个具有相同名称的类型引入同一作用域 + +如你所见,使用父模块可以区分这两个 `Result` 类型。如果我们是指定 `use std::fmt::Result` 和 `use std::io::Result`,我们将在同一作用域拥有了两个 `Result` 类型,当我们使用 `Result` 时,Rust 则不知道我们要用的是哪个。 + +### 使用 `as` 关键字提供新的名称 + +使用 `use` 将两个同名类型引入同一作用域这个问题还有另一个解决办法:在这个类型的路径后面,我们使用 `as` 指定一个新的本地名称或者别名。示例 7-16 展示了另一个编写示例 7-15 中代码的方法,通过 `as` 重命名其中一个 `Result` 类型。 + +文件名: src/lib.rs + +```rust +use std::fmt::Result; +use std::io::Result as IoResult; + +fn function1() -> Result { + // --snip-- +# Ok(()) +} + +fn function2() -> IoResult<()> { + // --snip-- +# Ok(()) +} +``` + +示例 7-16: 使用 `as` 关键字重命名引入作用域的类型 + +在第二个 `use` 语句中,我们选择 `IoResult` 作为 `std::io::Result` 的新名称,它与从 `std::fmt` 引入作用域的 `Result` 并不冲突。示例 7-15 和示例 7-16 都是惯用的,如何选择都取决于你! + +### 使用 `pub use` 重导出名称 + +当使用 `use` 关键字将名称导入作用域时,在新作用域中可用的名称是私有的。如果为了让调用你编写的代码的代码能够像在自己的作用域内引用这些类型,可以结合 `pub` 和 `use`。这个技术被称为 “重导出”(*re-exporting*),因为这样做将项引入作用域并同时使其可供其他代码引入自己的作用域。 + +示例 7-17 展示了将示例 7-11 中使用 `use` 的根模块变为 `pub use` 的版本的代码。 + +文件名: src/lib.rs + +```rust +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(); +} +# fn main() {} +``` + +示例 7-17: 通过 `pub use` 使名称可引入任何代码的作用域中 + +通过 `pub use`,现在可以通过新路径 `hosting::add_to_waitlist` 来调用 `add_to_waitlist` 函数。如果没有指定 `pub use`,`eat_at_restaurant` 函数可以在其作用域中调用 `hosting::add_to_waitlist`,但外部代码则不允许使用这个新路径。 + +当你的代码的内部结构与调用你的代码的程序员的思考领域不同时,重导出会很有用。例如,在这个餐馆的比喻中,经营餐馆的人会想到“前台”和“后台”。但顾客在光顾一家餐馆时,可能不会以这些术语来考虑餐馆的各个部分。使用 `pub use`,我们可以使用一种结构编写代码,却将不同的结构形式暴露出来。这样做可以使我们的库很好地将在开发这个库的程序员和调用这个库的程序员组织起来。 + +### 使用外部包 + +在第二章中我们编写了一个猜猜看游戏。那个项目使用了一个外部包,`rand`,来生成随机数。为了在项目中使用 `rand`,在 *Cargo.toml* 中加入了如下行: + +文件名: Cargo.toml + +```toml +[dependencies] +rand = "0.5.5" +``` + +在 *Cargo.toml* 中加入 `rand` 依赖告诉了 Cargo 要从 [crates.io](https://crates.io) 下载 `rand` 和其依赖,并使其可在项目代码中使用。 + +接着,为了将 `rand` 定义引入项目包的作用域,我们加入一行 `use` 起始的包名,它以 `rand` 包名开头并列出了需要引入作用域的项。回忆一下第二章的 “生成一个随机数” 部分,我们曾将 `Rng` trait 引入作用域并调用了 `rand::thread_rng` 函数: + +```rust,ignore +use rand::Rng; + +fn main() { + let secret_number = rand::thread_rng().gen_range(1, 101); +} +``` + +[crates.io](https://crates.io) 上有很多 Rust 社区成员发布的包,将其引入你自己的项目都需要一道相同的步骤:在 *Cargo.toml* 列出它们并通过 `use` 将其中定义的项引入项目包的作用域中。 + +注意标准库(`std`)对于你的包来说也是外部 crate。因为标准库随 Rust 语言一同分发,无需修改 *Cargo.toml* 来引入 `std`,不过需要通过 `use` 将标准库中定义的项引入项目包的作用域中来引用它们,比如我们使用的 `HashMap`: + +```rust +use std::collections::HashMap; +``` + +这是一个以标准库 crate 名 `std` 开头的绝对路径。 + +### 嵌套路径来消除大量的 `use` 行 + +当需要引入很多定义于相同包或相同模块的项时,为每一项单独列出一行会占用源码很大的空间。例如猜猜看章节示例 2-4 中有两行 `use` 语句都从 `std` 引入项到作用域: + +文件名: src/main.rs + +```rust +use std::cmp::Ordering; +use std::io; +// ---snip--- +``` + +相反,我们可以使用嵌套路径将相同的项在一行中引入作用域。这么做需要指定路径的相同部分,接着是两个冒号,接着是大括号中的各自不同的路径部分,如示例 7-18 所示。 + +文件名: src/main.rs + +```rust +use std::{cmp::Ordering, io}; +// ---snip--- +``` + +示例 7-18: 指定嵌套的路径在一行中将多个带有相同前缀的项引入作用域 + +在较大的程序中,使用嵌套路径从相同包或模块中引入很多项,可以显著减少所需的独立 `use` 语句的数量! + +我们可以在路径的任何层级使用嵌套路径,这在组合两个共享子路径的 `use` 语句时非常有用。例如,示例 7-19 中展示了两个 `use` 语句:一个将 `std::io` 引入作用域,另一个将 `std::io::Write` 引入作用域: + +文件名: src/lib.rs + +```rust +use std::io; +use std::io::Write; +``` + +示例 7-19: 通过两行 `use` 语句引入两个路径,其中一个是另一个的子路径 + +两个路径的相同部分是 `std::io`,这正是第一个路径。为了在一行 `use` 语句中引入这两个路径,可以在嵌套路径中使用 `self`,如示例 7-20 所示。 + +文件名: src/lib.rs + +```rust +use std::io::{self, Write}; +``` + +示例 7-20: 将示例 7-19 中部分重复的路径合并为一个 `use` 语句 + +这一行便将 `std::io` 和 `std::io::Write` 同时引入作用域。 + +### 通过 glob 运算符将所有的公有定义引入作用域 + +如果希望将一个路径下 **所有** 公有项引入作用域,可以指定路径后跟 `*`,glob 运算符: + +```rust +use std::collections::*; +``` + +这个 `use` 语句将 `std::collections` 中定义的所有公有项引入当前作用域。使用 glob 运算符时请多加小心!Glob 会使得我们难以推导作用域中有什么名称和它们是在何处定义的。 + +glob 运算符经常用于测试模块 `tests` 中,这时会将所有内容引入作用域;我们将在第十一章 “如何编写测试” 部分讲解。glob 运算符有时也用于 prelude 模式;查看 [标准库中的文档](https://doc.rust-lang.org/std/prelude/index.html#other-preludes) 了解这个模式的更多细节。 From 7b140087c78096479daf8425fc5138d21587626b Mon Sep 17 00:00:00 2001 From: shengurun Date: Mon, 28 Oct 2019 15:02:53 +0800 Subject: [PATCH 2/3] =?UTF-8?q?add:=20=E6=B7=BB=E5=8A=A0=E6=96=B0=E7=89=88?= =?UTF-8?q?ch07-05=E7=9A=84=E7=BF=BB=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...separating-modules-into-different-files.md | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 src/ch07-05-separating-modules-into-different-files.md diff --git a/src/ch07-05-separating-modules-into-different-files.md b/src/ch07-05-separating-modules-into-different-files.md new file mode 100644 index 0000000..8981597 --- /dev/null +++ b/src/ch07-05-separating-modules-into-different-files.md @@ -0,0 +1,64 @@ +## 将模块分割进不同文件 + +> [ch07-05-separating-modules-into-different-files.md](https://github.com/rust-lang/book/blob/master/src/ch07-05-separating-modules-into-different-files.md) +>
+> commit a5a5bf9d6ea5763a9110f727911a21da854b1d90 + +到目前为止,本章所有的例子都在一个文件中定义多个模块。当模块变得更大时,你可能想要将它们的定义移动到单独的文件中使代码更容易阅读。 + +例如,我们从示例 7-17 开始,将 `front_of_house` 模块移动到属于它自己的文件 *src/front_of_house.rs* 中,通过改变 crate 根文件,使其包含示例 7-21 所示的代码。在这个例子中,crate 根文件是 *src/lib.rs*,这也同样适用于以 *src/main.rs* 为 crate 根文件的二进制 crate 项。 + +文件名: src/lib.rs + +```rust,ignore +mod front_of_house; + +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-21: 声明 `front_of_house` 模块,其内容位于 *src/front_of_house.rs* + +*src/front_of_house.rs* 会获取 `front_of_house` 模块的定义内容,如示例 7-22 所示。 + +文件名: src/front_of_house.rs + +```rust +pub mod hosting { + pub fn add_to_waitlist() {} +} +``` + +示例 7-22: 在 *src/front_of_house.rs* 中定义 `front_of_house` +模块 + +在 `mod front_of_house` 后使用分号,而不是代码块,这将告诉 Rust 在另一个与模块同名的文件中加载模块的内容。继续重构我们例子,将 `hosting` 模块也提取到其自己的文件中,仅对 *src/front_of_house.rs* 包含 `hosting` 模块的声明进行修改: + +文件名: src/front_of_house.rs + +```rust +pub mod hosting; +``` + +接着我们创建一个 *src/front_of_house* 目录和一个包含 `hosting` 模块定义的 *src/front_of_house/hosting.rs* 文件: + +文件名: src/front_of_house/hosting.rs + +``` +pub fn add_to_waitlist() {} +``` + +模块树依然保持相同,`eat_at_restaurant` 中的函数调用也无需修改继续保持有效,即便其定义存在于不同的文件中。这个技巧让你可以在模块代码增长时,将它们移动到新文件中。 + +注意,*src/lib.rs* 中的 `pub use crate::front_of_house::hosting` 语句是没有改变的,在文件作为 crate 的一部分而编译时,`use` 不会有任何影响。`mod` 关键字声明了模块,Rust 会在与模块同名的文件中查找模块的代码。 + +## 总结 + +Rust 提供了将包组织进 crate、将 crate 组织进模块和通过指定绝对或相对路径从一个模块引用另一个模块中定义的项的方式。你可以通过使用 `use` 语句将路径引入作用域,这样在多次使用时可以使用更短的路径。模块定义的代码默认是私有的,不过可以选择增加 `pub` 关键字使其定义变为公有。 + +接下来,让我们看看一些标准库提供的集合数据类型,你可以利用它们编写出漂亮整洁的代码。 From a8cc6fc499dc4d3073729a03d98aee353e2b9110 Mon Sep 17 00:00:00 2001 From: shengurun Date: Mon, 28 Oct 2019 15:24:33 +0800 Subject: [PATCH 3/3] =?UTF-8?q?del:=20=E5=88=A0=E9=99=A4=E7=AC=AC=E4=B8=83?= =?UTF-8?q?=E7=AB=A0=E7=9A=84=E8=BF=87=E6=97=B6=E7=AB=A0=E8=8A=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 删除第七章的过时章节。 2. 对ch07-04中的错误进行修复。 3. 修改SUMMARY.md,以便可以使用mdbook进行最新版第七章的编译。 --- src/SUMMARY.md | 9 +- src/ch07-00-packages-crates-and-modules.md | 16 - src/ch07-01-mod-and-the-filesystem.md | 361 --------- ...es-for-making-libraries-and-executables.md | 31 - ...ch07-02-controlling-visibility-with-pub.md | 237 ------ ...es-and-use-to-control-scope-and-privacy.md | 727 ------------------ src/ch07-03-importing-names-with-use.md | 224 ------ ...g-paths-into-scope-with-the-use-keyword.md | 2 +- 8 files changed, 7 insertions(+), 1600 deletions(-) delete mode 100644 src/ch07-00-packages-crates-and-modules.md delete mode 100644 src/ch07-01-mod-and-the-filesystem.md delete mode 100644 src/ch07-01-packages-and-crates-for-making-libraries-and-executables.md delete mode 100644 src/ch07-02-controlling-visibility-with-pub.md delete mode 100644 src/ch07-02-modules-and-use-to-control-scope-and-privacy.md delete mode 100644 src/ch07-03-importing-names-with-use.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index a4c7b08..79923c4 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -36,9 +36,12 @@ ## 基本 Rust 技能 -- [包、crate 与模块](ch07-00-packages-crates-and-modules.md) - - [包和 crate 用来创建库和二进制项目](ch07-01-packages-and-crates-for-making-libraries-and-executables.md) - - [模块系统用来控制作用域和私有性](ch07-02-modules-and-use-to-control-scope-and-privacy.md) +- [使用包、Crate和模块管理不断增长的项目](ch07-00-managing-growing-projects-with-packages-crates-and-modules.md) + - [包和 crate](ch07-01-packages-and-crates.md) + - [定义模块来控制作用域与私有性](ch07-02-defining-modules-to-control-scope-and-privacy.md) + - [路径来引用模块树中的项](ch07-03-paths-for-referring-to-an-item-in-the-module-tree.md) + - [使用 `use` 关键字将名称引入作用域](ch07-04-bringing-paths-into-scope-with-the-use-keyword.md) + - [将模块分割进不同文件](ch07-05-separating-modules-into-different-files.md) - [常见集合](ch08-00-common-collections.md) - [vector](ch08-01-vectors.md) diff --git a/src/ch07-00-packages-crates-and-modules.md b/src/ch07-00-packages-crates-and-modules.md deleted file mode 100644 index e8778af..0000000 --- a/src/ch07-00-packages-crates-and-modules.md +++ /dev/null @@ -1,16 +0,0 @@ -# 包、crate 与 模块 - -> [ch07-00-packages-crates-and-modules.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch07-00-modules.md) ->
-> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f - -编写程序时一个核心的问题是 **作用域**(*scope*):在代码的某处编译器知道哪些变量名?允许调用哪些函数?这些变量引用的又是什么? - -Rust 有一系列与作用域相关的功能。这有时被称为 “模块系统”(“the module system”),不过又不仅仅是模块: - -* **包**(*Packages*)是 Cargo 的一个功能,它允许你构建、测试和分享 crate。 -* *Crates* 是一个模块的树形结构,它形成了库或二进制项目。 -* **模块**(*Modules*)和 *use* 关键字允许你控制作用域和路径的私有性。 -* **路径**(*path*)是一个命名例如结构体、函数或模块等项的方式 - -本章将会覆盖所有这些概念。很快我们就能像专家一样将命名引入作用域、定义作用域和将命名导出到作用域! diff --git a/src/ch07-01-mod-and-the-filesystem.md b/src/ch07-01-mod-and-the-filesystem.md deleted file mode 100644 index e62782b..0000000 --- a/src/ch07-01-mod-and-the-filesystem.md +++ /dev/null @@ -1,361 +0,0 @@ -## `mod` 和文件系统 - -> [ch07-01-mod-and-the-filesystem.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch07-01-mod-and-the-filesystem.md) ->
-> commit a120c730714e07f8f32d905e9374a50b2e0ffdf5 - -我们将通过使用 Cargo 创建一个新项目来开始我们的模块之旅,不过这次不再创建一个二进制 crate,而是创建一个库 crate:一个其他人可以作为依赖导入的项目。第二章猜猜看游戏中作为依赖使用的 `rand` 就是这样的 crate。 - -我们将创建一个库的框架,提供一些通用的网络功能;我们将专注于模块和函数的组织,而不必担心函数体中的具体代码。这个项目叫做 `communicator`。若要创建一个库,应当使用 `--lib` 参数而不是之前所用的 `--bin` 参数: - -```text -$ cargo new communicator --lib -$ cd communicator -``` - -注意 Cargo 生成了 *src/lib.rs* 而不是 *src/main.rs*。在 *src/lib.rs* 中我们会找到这些: - -文件名: src/lib.rs - -```rust -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - assert_eq!(2 + 2, 4); - } -} -``` - -Cargo 创建了一个空的测试来帮助我们开始库项目,不像使用 `--bin` 参数那样创建一个 “Hello, world!” 二进制项目。在本章之后的 “使用 `super` 访问父模块” 部分会介绍 `#[]` 和 `mod tests` 语法,目前只需确保它们位于 *src/lib.rs* 底部即可。 - -因为没有 *src/main.rs* 文件,所以没有可供 Cargo 的 `cargo run` 执行的东西。因此,我们将只使用 `cargo build` 命令编译库 crate 的代码。 - -我们将学习根据编写代码的意图来以不同方法组织库项目代码以适应多种情况。 - -### 模块定义 - -对于 `communicator` 网络库,首先要定义一个叫做 `network` 的模块,它包含一个叫做 `connect` 的函数定义。Rust 中所有模块的定义都以关键字 `mod` 开始。在 *src/lib.rs* 文件的开头在测试代码的上面增加这些代码: - -文件名: src/lib.rs - -```rust -mod network { - fn connect() { - } -} -``` - -`mod` 关键字的后面是模块的名字,`network`,接着是位于大括号中的代码块。代码块中的一切都位于 `network` 命名空间中。在这个例子中,只有一个函数,`connect`。如果想要在 `network` 模块外面的代码中调用这个函数,需要指定模块名并使用命名空间语法 `::`,像这样:`network::connect()`。 - -也可以在 *src/lib.rs* 文件中同时存在多个模块。例如,再拥有一个 `client` 模块,它也有一个叫做 `connect` 的函数,如示例 7-1 中所示那样增加这个模块: - -文件名: src/lib.rs - -```rust -mod network { - fn connect() { - } -} - -mod client { - fn connect() { - } -} -``` - -示例 7-1:`network` 模块和 `client` 一同定义于 *src/lib.rs* - -现在我们有了 `network::connect` 函数和 `client::connect` 函数。它们可能有着完全不同的功能,同时它们也不会彼此冲突,因为它们位于不同的模块。 - -在这个例子中,因为我们构建的是一个库,作为库入口点的文件是 *src/lib.rs*。然而,对于创建模块来说,*src/lib.rs* 并没有什么特殊意义。也可以在二进制 crate 的 *src/main.rs* 中创建模块,正如在库 crate 的 *src/lib.rs* 创建模块一样。事实上,也可以将模块放入其他模块中。这有助于随着模块的增长,将相关的功能组织在一起并又保持各自独立。选择组织代码的方式取决于如何考虑代码各部分之间的关系。例如,对于库的用户来说,`client` 模块和它的函数 `connect` 可能放在 `network` 命名空间里显得更有道理,如示例 7-2 所示: - -文件名: src/lib.rs - -```rust -mod network { - fn connect() { - } - - mod client { - fn connect() { - } - } -} -``` - -示例 7-2:将 `client` 模块移动到 `network` 模块中 - -在 *src/lib.rs* 文件中,将现有的 `mod network` 和 `mod client` 的定义替换为示例 7-2 中的定义,这里将 `client` 模块作为 `network` 的一个内部模块。现在我们有了 `network::connect` 和 `network::client::connect` 函数:它们都叫 `connect` ,但它们并不互相冲突,因为它们在不同的命名空间中。 - -这样,模块之间形成了一个层次结构。*src/lib.rs* 的内容位于最顶层,而其子模块位于较低的层次。如下是示例 7-1 中的例子以层次的方式考虑的结构: - -```text -communicator - ├── network - └── client -``` - -而这是示例 7-2 中例子的层次结构: - -```text -communicator - └── network - └── client -``` - -可以看到示例 7-2 中,`client` 是 `network` 的子模块,而不是它的同级模块。更为复杂的项目可以有很多的模块,所以它们需要符合逻辑地组合在一起以便记录它们。在项目中 “符合逻辑” 的意义全凭你的理解和库的用户对你项目领域的认识。利用我们这里讲到的技术来创建同级模块和嵌套的模块,总有一个会是你会喜欢的结构。 - -### 将模块移动到其他文件 - -位于层级结构中的模块,非常类似计算机领域的另一个我们非常熟悉的结构:文件系统!我们可以利用 Rust 的模块系统连同多个文件一起分解 Rust 项目,这样就不会是所有的内容都落到 *src/lib.rs* 或 *src/main.rs* 中了。为了举例,我们将从示例 7-3 中的代码开始: - -文件名: src/lib.rs - -```rust -mod client { - fn connect() { - } -} - -mod network { - fn connect() { - } - - mod server { - fn connect() { - } - } -} -``` - -示例 7-3:三个模块,`client`、`network` 和 `network::server`,它们都定义于 *src/lib.rs* - -*src/lib.rs* 文件有如下层次结构: - -```text -communicator - ├── client - └── network - └── server -``` - -如果这些模块有很多函数,而这些函数又很长,将难以在文件中寻找我们需要的代码。因为这些函数被嵌套进一个或多个 `mod` 块中,同时函数中的代码也会开始变长。这就有充分的理由将 `client`、`network` 和 `server` 每一个模块从 *src/lib.rs* 抽出并放入它们自己的文件中。 - -首先,将 `client` 模块的代码替换为只有 `client` 模块声明,这样 *src/lib.rs* 看起来应该像如示例 7-4 所示: - -文件名: src/lib.rs - -```rust,ignore -mod client; - -mod network { - fn connect() { - } - - mod server { - fn connect() { - } - } -} -``` - -示例 7-4:提取出 `client` 模块的内容但仍将其声明留在 *src/lib.rs* - -这里我们仍然 **声明** 了 `client` 模块,不过将代码块替换为了分号,这告诉了 Rust 在 `client` 模块的作用域中寻找另一个定义代码的位置。换句话说,`mod client;` 行意味着: - -```rust,ignore -mod client { - // contents of client.rs -} -``` - -那么现在需要创建对应模块名的外部文件。在 *src/* 目录创建一个 *client.rs* 文件,接着打开它并输入如下内容,它是上一步被去掉的 `client` 模块中的 `connect` 函数: - -文件名: src/client.rs - -```rust -fn connect() { -} -``` - -注意这个文件中并不需要一个 `mod` 声明;因为已经在 *src/lib.rs* 中已经使用 `mod` 声明了 `client` 模块。这个文件仅仅提供 `client` 模块的 **内容**。如果在这里加上一个 `mod client`,那么就等于给 `client` 模块增加了一个叫做 `client` 的子模块了! - -Rust 默认只知道 *src/lib.rs* 中的内容。如果想要对项目加入更多文件,我们需要在 *src/lib.rs* 中告诉 Rust 去寻找其他文件;这就是为什么 `mod client` 需要被定义在 *src/lib.rs* 而不能在 *src/client.rs* 的原因。 - -现在,一切应该能成功编译,虽然会有一些警告。记住使用 `cargo build` 而不是 `cargo run`, 因为这是一个库 crate 而不是二进制 crate: - -```text -$ cargo build - Compiling communicator v0.1.0 (file:///projects/communicator) -warning: function is never used: `connect` - --> src/client.rs:1:1 - | -1 | / fn connect() { -2 | | } - | |_^ - | - = note: #[warn(dead_code)] on by default - -warning: function is never used: `connect` - --> src/lib.rs:4:5 - | -4 | / fn connect() { -5 | | } - | |_____^ - -warning: function is never used: `connect` - --> src/lib.rs:8:9 - | -8 | / fn connect() { -9 | | } - | |_________^ -``` - -这些警告提醒我们有从未被使用的函数。目前不用担心这些警告,在本章后面的 “使用 `pub` 控制可见性” 部分会解决它们。好消息是,它们仅仅是警告,我们的项目能够成功编译。 - -下面使用相同的模式将 `network` 模块提取到自己的文件中。删除 *src/lib.rs* 中 `network` 模块的内容并在声明后加上一个分号,像这样: - -文件名: src/lib.rs - -```rust,ignore -mod client; - -mod network; -``` - -接着新建 *src/network.rs* 文件并输入如下内容: - -文件名: src/network.rs - -```rust -fn connect() { -} - -mod server { - fn connect() { - } -} -``` - -注意这个模块文件中我们也使用了一个 `mod` 声明;这是因为我们希望 `server` 成为 `network` 的一个子模块。 - -现在再次运行 `cargo build`。成功!不过我们还需要再提取出另一个模块:`server`。因为这是一个子模块——也就是模块中的模块——目前的将模块提取到对应名字的文件中的策略就不管用了。如果我们仍这么尝试则会出现错误。对 *src/network.rs* 的第一个修改是用 `mod server;` 替换 `server` 模块的内容: - -文件名: src/network.rs - -```rust,ignore -fn connect() { -} - -mod server; -``` - -接着创建 *src/server.rs* 文件并输入需要提取的 `server` 模块的内容: - -文件名: src/server.rs - -```rust -fn connect() { -} -``` - -当尝试运行 `cargo build` 时,会出现如示例 7-5 中所示的错误: - -```text -$ cargo build - Compiling communicator v0.1.0 (file:///projects/communicator) -error: cannot declare a new module at this location - --> src/network.rs:4:5 - | -4 | mod server; - | ^^^^^^ - | -note: maybe move this module `src/network.rs` to its own directory via `src/network/mod.rs` - --> src/network.rs:4:5 - | -4 | mod server; - | ^^^^^^ -note: ... or maybe `use` the module `server` instead of possibly redeclaring it - --> src/network.rs:4:5 - | -4 | mod server; - | ^^^^^^ -``` - -示例 7-5:尝试将 `server` 子模块提取到 *src/server.rs* 时出现的错误 - -这个错误说明 “不能在这个位置新声明一个模块” 并指出 *src/network.rs* 中的 `mod server;` 这一行。看来 *src/network.rs* 与 *src/lib.rs* 在某些方面是不同的;继续阅读以理解这是为什么。 - -示例 7-5 中间的 note 事实上是非常有帮助的,因为它指出了一些我们还未讲到的操作: - -```text -note: maybe move this module `network` to its own directory via -`network/mod.rs` -``` - -我们可以按照记录所建议的去操作,而不是继续使用之前的与模块同名文件的模式: - -1. 新建一个叫做 *network* 的 **目录**,这是父模块的名字 -2. 将 *src/network.rs* 移动到新建的 *network* 目录中并重命名为 *src/network/mod.rs* -3. 将子模块文件 *src/server.rs* 移动到 *network* 目录中 - -如下是执行这些步骤的命令: - -```text -$ mkdir src/network -$ mv src/network.rs src/network/mod.rs -$ mv src/server.rs src/network -``` - -现在如果运行 `cargo build` 的话将顺利编译(虽然仍有警告)。现在模块的布局看起来仍然与示例 7-3 中所有代码都在 *src/lib.rs* 中时完全一样: - -```text -communicator - ├── client - └── network - └── server -``` - -对应的文件布局现在看起来像这样: - -```text -└── src - ├── client.rs - ├── lib.rs - └── network - ├── mod.rs - └── server.rs -``` - -那么,当我们想要提取 `network::server` 模块时,为什么也必须将 *src/network.rs* 文件改名成 *src/network/mod.rs* 文件呢,还有为什么要将 `network::server` 的代码放入 *network* 目录的 *src/network/server.rs* 文件中呢?原因是如果 *server.rs* 文件在 *src* 目录中那么 Rust 就不能知道 `server` 应当是 `network` 的子模块。为了阐明这里 Rust 的行为,让我们考虑一下有着如下层级的另一个例子,其所有定义都位于 *src/lib.rs* 中: - -```text -communicator - ├── client - └── network - └── client -``` - -在这个例子中,仍然有这三个模块,`client`、`network` 和 `network::client`。如果按照与上面最开始将模块提取到文件中相同的步骤来操作,对于 `client` 模块会创建 *src/client.rs*。对于 `network` 模块,会创建 *src/network.rs*。但是接下来不能将 `network::client` 模块提取到 *src/client.rs* 文件中,因为它已经存在了,对应顶层的 `client` 模块!如果将 `client` 和 `network::client` 的代码都放入 *src/client.rs* 文件,Rust 将无从可知这些代码是属于 `client` 还是 `network::client` 的。 - -因此,为了将 `network` 模块的子模块 `network::client` 提取到一个文件中,需要为 `network` 模块新建一个目录替代 *src/network.rs* 文件。接着 `network` 模块的代码将进入 *src/network/mod.rs* 文件,而子模块 `network::client` 将拥有其自己的文件 *src/network/client.rs*。现在顶层的 *src/client.rs* 中的代码毫无疑问的都属于 `client` 模块。 - -### 模块文件系统的规则 - -让我们总结一下与文件有关的模块规则: - -* 如果一个叫做 `foo` 的模块没有子模块,应该将 `foo` 的声明放入叫做 *foo.rs* 的文件中。 -* 如果一个叫做 `foo` 的模块有子模块,应该将 `foo` 的声明放入叫做 *foo/mod.rs* 的文件中。 - -这些规则适用于递归(嵌套),所以如果 `foo` 模块有一个子模块 `bar` 而 `bar` 没有子模块,则 *src* 目录中应该有如下文件: - -```text -└── foo - ├── bar.rs (contains the declarations in `foo::bar`) - └── mod.rs (contains the declarations in `foo`, including `mod bar`) -``` - -模块自身则应该使用 `mod` 关键字定义于父模块的文件中。 - -接下来,我们讨论一下 `pub` 关键字,并除掉那些警告! diff --git a/src/ch07-01-packages-and-crates-for-making-libraries-and-executables.md b/src/ch07-01-packages-and-crates-for-making-libraries-and-executables.md deleted file mode 100644 index 82345d8..0000000 --- a/src/ch07-01-packages-and-crates-for-making-libraries-and-executables.md +++ /dev/null @@ -1,31 +0,0 @@ -## 包和 crate 用来创建库和二进制项目 - -> [ch07-01-mod-and-the-filesystem.md](https://github.com/rust-lang/book/blob/master/src/ch07-01-packages-and-crates-for-making-libraries-and-executables.md) ->
-> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f - -让我们聊聊 **模块** 与 *crate*。下面是一个总结: - -* *crate* 是一个二进制或库项目。 -* **crate 根**(*crate root*)是一个用来描述如何构建 crate 的文件。 -* 带有 *Cargo.toml* 文件的 **包** 用以描述如何构建一个或多个 crate。一个包中至多可以有一个库项目。 - -所以当运行 `cargo new` 时是在创建一个包: - -```text -$ cargo new my-project - Created binary (application) `my-project` package -$ ls my-project -Cargo.toml -src -$ ls my-project/src -main.rs -``` - -因为 Cargo 创建了 *Cargo.toml*,这意味着现在我们有了一个包。如果查看 *Cargo.toml* 的内容,会发现并没有提到 *src/main.rs*。然而,Cargo 的约定是如果在代表包的 *Cargo.toml* 的同级目录下包含 *src* 目录且其中包含 *main.rs* 文件的话,Cargo 就知道这个包带有一个与包同名的二进制 crate,且 *src/main.rs* 就是 crate 根。另一个约定如果包目录中包含 *src/lib.rs*,则包带有与其同名的库 crate,且 *src/lib.rs* 是 crate 根。crate 根文件将由 Cargo 传递给 `rustc` 来实际构建库或者二进制项目。 - -一个包可以带有零个或一个库 crate 和任意多个二进制 crate。一个包中必须带有至少一个(库或者二进制)crate。 - -如果包同时包含 *src/main.rs* 和 *src/lib.rs*,那么它带有两个 crate:一个库和一个二进制项目,同名。如果只有其中之一,则包将只有一个库或者二进制 crate。包可以带有多个二进制 crate,需将其文件置于 *src/bin* 目录;每个文件将是一个单独的二进制 crate。 - -接下来让我们讨论模块! diff --git a/src/ch07-02-controlling-visibility-with-pub.md b/src/ch07-02-controlling-visibility-with-pub.md deleted file mode 100644 index c5f7a0d..0000000 --- a/src/ch07-02-controlling-visibility-with-pub.md +++ /dev/null @@ -1,237 +0,0 @@ -## 使用 `pub` 控制可见性 - -> [ch07-02-controlling-visibility-with-pub.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch07-02-controlling-visibility-with-pub.md) ->
-> commit a120c730714e07f8f32d905e9374a50b2e0ffdf5 - -我们通过将 `network` 和 `network::server` 的代码分别移动到 *src/network/mod.rs* 和 *src/network/server.rs* 文件中解决了示例 7-5 中出现的错误信息。现在,`cargo build` 能够构建我们的项目,不过仍然有一些警告信息,表示 `client::connect`、`network::connect` 和`network::server::connect` 函数没有被使用: - -```text -warning: function is never used: `connect` - --> src/client.rs:1:1 - | -1 | / fn connect() { -2 | | } - | |_^ - | - = note: #[warn(dead_code)] on by default - -warning: function is never used: `connect` - --> src/network/mod.rs:1:1 - | -1 | / fn connect() { -2 | | } - | |_^ - -warning: function is never used: `connect` - --> src/network/server.rs:1:1 - | -1 | / fn connect() { -2 | | } - | |_^ -``` - -那么为什么会出现这些警告信息呢?毕竟我们构建的是一个库,它的函数的目的是被 **用户** 使用,而不一定要被项目自身使用,所以不应该担心这些 `connect` 函数是未使用的。创建它们的意义就在于被另一个项目而不是被我们自己使用。 - -为了理解为什么这个程序出现了这些警告,尝试在另一个项目中使用这个 `connect` 库,从外部调用它们。为此,通过创建一个包含这些代码的 *src/main.rs* 文件,在与库 crate 相同的目录创建一个二进制 crate: - -文件名: src/main.rs - -```rust,ignore -extern crate communicator; - -fn main() { - communicator::client::connect(); -} -``` - -使用 `extern crate` 指令将 `communicator` 库 crate 引入到作用域。我们的包现在包含 **两个** crate。Cargo 认为 *src/main.rs* 是一个二进制 crate 的根文件,与现存的以 *src/lib.rs* 为根文件的库 crate 相区分。这个模式在可执行项目中非常常见:大部分功能位于库 crate 中,而二进制 crate 使用该库 crate。通过这种方式,其他程序也可以使用这个库 crate,这是一个很好的关注分离(separation of concerns)。 - -从一个外部 crate 的视角观察 `communicator` 库的内部,我们创建的所有模块都位于一个与 crate 同名的模块内部,`communicator`。这个顶层的模块被称为 crate 的 **根模块**(*root module*)。 - -另外注意到即便在项目的子模块中使用外部 crate,`extern crate` 也应该位于根模块(也就是 *src/main.rs* 或 *src/lib.rs*)。接着,在子模块中,我们就可以像顶层模块那样引用外部 crate 中的项了。 - -我们的二进制 crate 如今正好调用了库中 `client` 模块的 `connect` 函数。然而,执行 `cargo build` 会在之前的警告之后出现一个错误: - -```text -error[E0603]: module `client` is private - --> src/main.rs:4:5 - | -4 | communicator::client::connect(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -``` - -啊哈!这告诉了我们 `client` 模块是私有的,这也正是那些警告的症结所在。这也是我们第一次在 Rust 上下文中涉及到 **公有**(*public*)和 **私有**(*private*)的概念。Rust 所有代码的默认状态是私有的:除了自己之外别人不允许使用这些代码。如果不在自己的项目中使用一个私有函数,因为程序自身是唯一允许使用这个函数的代码,Rust 会警告说函数未被使用。 - -一旦我们指定一个像 `client::connect` 的函数为公有,不光二进制 crate 中的函数调用是允许的,函数未被使用的警告也会消失。将其标记为公有让 Rust 知道了函数将会在程序的外部被使用。现在这个可能的理论上的外部可用性使得 Rust 认为这个函数 “已经被使用”。因此。当某项被标记为公有,Rust 不再要求它在程序自身被使用并停止警告函数未被使用。 - -### 标记函数为公有 - -为了告诉 Rust 将函数标记为公有,在声明的开头增加 `pub` 关键字。现在我们将致力于修复 `client::connect` 未被使用的警告,以及二进制 crate 中 “模块 `client` 是私有的” 的错误。像这样修改 *src/lib.rs* 使 `client` 模块公有: - -文件名: src/lib.rs - -```rust,ignore -pub mod client; - -mod network; -``` - -`pub` 写在 `mod` 之前。再次尝试构建: - -```text -error[E0603]: function `connect` is private - --> src/main.rs:4:5 - | -4 | communicator::client::connect(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -``` - -非常好!另一个不同的错误!好的,不同的错误信息也是值得庆祝的。新错误表明“函数 `connect` 是私有的”,那么让我们修改 *src/client.rs* 将 `client::connect` 也设为公有: - -文件名: src/client.rs - -```rust -pub fn connect() { -} -``` - -再一次运行 `cargo build`: - -```text -warning: function is never used: `connect` - --> src/network/mod.rs:1:1 - | -1 | / fn connect() { -2 | | } - | |_^ - | - = note: #[warn(dead_code)] on by default - -warning: function is never used: `connect` - --> src/network/server.rs:1:1 - | -1 | / fn connect() { -2 | | } - | |_^ -``` - -编译通过了,关于 `client::connect` 未被使用的警告消失了! - -未被使用的代码并不总是意味着它们需要被设为公有的:如果你 **不** 希望这些函数成为公有 API 的一部分,未被使用的代码警告可能是在提醒你这些代码不再需要并可以安全地删除它们。这也可能是警告你出 bug 了,如果你刚刚不小心删除了库中所有这个函数的调用。 - -当然我们的情况是,**确实** 希望另外两个函数也作为 crate 公有 API 的一部分,所以让我们也将其标记为 `pub` 并去掉剩余的警告。修改 *src/network/mod.rs* 为: - -文件名: src/network/mod.rs - -```rust,ignore -pub fn connect() { -} - -mod server; -``` - -并编译代码: - -```text -warning: function is never used: `connect` - --> src/network/mod.rs:1:1 - | -1 | / pub fn connect() { -2 | | } - | |_^ - | - = note: #[warn(dead_code)] on by default - -warning: function is never used: `connect` - --> src/network/server.rs:1:1 - | -1 | / fn connect() { -2 | | } - | |_^ -``` - -虽然将 `network::connect` 设为 `pub` 了,我们仍然得到了一个未被使用函数的警告。这是因为模块中的函数是公有的,不过函数所在的 `network` 模块却不是公有的。这回我们是自内向外修改库文件的,而 `client::connect` 的时候是自外向内修改的。我们需要修改 *src/lib.rs* 让 `network` 也是公有的,如下: - -文件名: src/lib.rs - -```rust,ignore -pub mod client; - -pub mod network; -``` - -现在编译的话,那个警告就消失了: - -```text -warning: function is never used: `connect` - --> src/network/server.rs:1:1 - | -1 | / fn connect() { -2 | | } - | |_^ - | - = note: #[warn(dead_code)] on by default -``` - -只剩一个警告了!尝试自食其力修改它吧! - -### 私有性规则 - -总的来说,有如下可见性规则: - -1. 如果一个项是公有的,它能被任何父模块访问 -2. 如果一个项是私有的,它能被其直接父模块及其任何子模块访问 - -### 私有性示例 - -让我们看看更多私有性的例子作为练习。创建一个新的库项目并在新项目的 *src/lib.rs* 输入示例 7-6 中的代码: - -文件名: src/lib.rs - -```rust,ignore -mod outermost { - pub fn middle_function() {} - - fn middle_secret_function() {} - - mod inside { - pub fn inner_function() {} - - fn secret_function() {} - } -} - -fn try_me() { - outermost::middle_function(); - outermost::middle_secret_function(); - outermost::inside::inner_function(); - outermost::inside::secret_function(); -} -``` - -示例 7-6:私有和公有函数的例子,其中部分是不正确的 - -在尝试编译这些代码之前,猜测一下 `try_me` 函数的哪一行会出错。接着编译项目来看看是否猜对了,然后继续阅读后面关于错误的讨论! - -#### 检查错误 - -`try_me` 函数位于项目的根模块。叫做 `outermost` 的模块是私有的,不过第二条私有性规则说明 `try_me` 函数允许访问 `outermost` 模块,因为 `outermost` 位于当前(根)模块,`try_me` 也是。 - -`outermost::middle_function` 的调用是正确的。因为 `middle_function` 是公有的,而 `try_me` 通过其父模块 `outermost` 访问 `middle_function`。根据上一段的规则我们可以确定这个模块是可访问的。 - -`outermost::middle_secret_function` 的调用会造成一个编译错误。`middle_secret_function` 是私有的,所以第二条(私有性)规则生效了。根模块既不是 `middle_secret_function` 的当前模块(`outermost` 是),也不是 `middle_secret_function` 当前模块的子模块。 - -叫做 `inside` 的模块是私有的且没有子模块,所以它只能被当前模块 `outermost` 访问。这意味着 `try_me` 函数不允许调用 `outermost::inside::inner_function` 或 `outermost::inside::secret_function` 中的任何一个。 - -#### 修改错误 - -这里有一些尝试修复错误的代码修改意见。在你尝试它们之前,猜测一下它们哪个能修复错误,接着编译查看你是否猜对了,并结合私有性规则理解为什么。 - -* 如果 `inside` 模块是公有的? -* 如果 `outermost` 是公有的而 `inside` 是私有的? -* 如果在 `inner_function` 函数体中调用 `::outermost::middle_secret_function()`?(开头的两个冒号意味着从根模块开始引用模块。) - -请随意设计更多的实验并尝试理解它们! - -接下来,让我们讨论一下使用 `use` 关键字将项引入作用域。 diff --git a/src/ch07-02-modules-and-use-to-control-scope-and-privacy.md b/src/ch07-02-modules-and-use-to-control-scope-and-privacy.md deleted file mode 100644 index 4dacb53..0000000 --- a/src/ch07-02-modules-and-use-to-control-scope-and-privacy.md +++ /dev/null @@ -1,727 +0,0 @@ -## 模块系统用来控制作用域和私有性 - -> [ch07-01-mod-and-the-filesystem.md](https://github.com/rust-lang/book/blob/master/src/ch07-02-modules-and-use-to-control-scope-and-privacy.md) ->
-> commit 9d431b25ca6c7200eafe4107eb0765c088923027 - -Rust 的此部分功能通常被引用为 “模块系统”(“the module system”),不过其包括了一些除模块之外的功能。在本部分我们会讨论: - -* 模块,一个组织代码和控制路径私有性的方式 -* 路径,一个命名项(item)的方式 -* `use` 关键字用来将路径引入作用域 -* `pub` 关键字使项变为公有 -* `as` 关键字用于将项引入作用域时进行重命名 -* 使用外部包 -* 嵌套路径用来消除大量的 `use` 语句 -* 使用 glob 运算符将模块的所有内容引入作用域 -* 如何将不同模块分割到单独的文件中 - -首先讲讲模块。模块允许我们将代码组织起来。示例 7-1 是一个例子,这些代码定义了名为 `sound` 的模块,其包含名为 `guitar` 的函数。 - -文件名: src/main.rs - -```rust -mod sound { - fn guitar() { - // 函数体 - } -} - -fn main() { - -} -``` - -示例 7-1: 包含 `guitar` 函数和 `main` 函数的 `sound` 模块 - -这里定义了两个函数,`guitar` 和 `main`。`guitar` 函数定义于 `mod` 块中。这个块定义了 `sound` 模块。 - -为了将代码组织到模块层次体系中,可以将模块嵌套进其他模块,如示例 7-2 所示: - -文件名: src/main.rs - -```rust -mod sound { - mod instrument { - mod woodwind { - fn clarinet() { - // 函数体 - } - } - } - - mod voice { - - } -} - -fn main() { - -} -``` - -示例 7-2: 模块中的模块 - -在这个例子中,我们像示例 7-1 一样定义了 `sound` 模块。接着在 `sound` 模块中定义了 `instrument` 和 `voice` 模块。`instrument` 模块中定义了另一个模块 `woodwind`,这个模块包含一个函数 `clarinet`。 - -在 “包和 crate 用来创建库和二进制项目” 部分提到 *src/main.rs* 和 *src/lib.rs* 被称为 **crate 根**。他们被称为 crate 根是因为这两个文件在 crate 模块树的根组成了名为 `crate` 模块。所以示例 7-2 中,有如示例 7-3 所示的模块树: - -```text -crate -└── sound - ├── instrument - │ └── woodwind - └── voice -``` - -示例 7-3: 示例 7-2 中代码的模块树 - -这个树展示了模块如何嵌套在其他模块中(比如 `woodwind` 嵌套在 `instrument` 中)以及模块如何作为其他模块的子模块的(`instrument` 和 `voice` 都定义在 `sound` 中)。整个模块树都位于名为 `crate` 这个隐式模块的根下。 - -这个树可能会令你想起电脑上文件系统的目录树;这是一个非常恰当的比喻!就像文件系统的目录,将代码放入任意模块也将创建对应的组织结构体。另一个相似点是为了引用文件系统或模块树中的项,需要使用 **路径**(*path*)。 - -### 路径用来引用模块树中的项 - -如果想要调用函数,需要知道其 **路径**。“路径” 是 “名称”(“name”) 的同义词,不过它用于文件系统语境。另外,函数、结构体和其他项可能会有多个指向相同项的路径,所以 “名称” 这个概念不太准确。 - -**路径** 可以有两种形式: - -* **绝对路径**(*absolute path*)从 crate 根开始,以 crate 名或者字面值 `crate` 开头。 -* **相对路径**(*relative path*)从当前模块开始,以 `self`、`super` 或当前模块的标识符开头。 - -绝对路径和相对路径都后跟一个或多个由双冒号(`::`)分割的标识符。 - -如何在示例 7-2 的 `main` 函数中调用 `clarinet` 函数呢?也就是说,`clarinet` 函数的路径是什么呢?在示例 7-4 中稍微简化了代码,移除了一些模块,并展示了两种在 `main` 中调用 `clarinet` 函数的方式。这个例子还不能编译,我们会解释为什么。 - -文件名: src/main.rs - -```rust,ignore,does_not_compile -mod sound { - mod instrument { - fn clarinet() { - // 函数体 - } - } -} - -fn main() { - // 绝对路径 - crate::sound::instrument::clarinet(); - - // Relative path - sound::instrument::clarinet(); -} -``` - -示例 7-4: 在简化的模块树中,分别使用绝对路径和相对路径在 `main` 中调用 `clarinet` 函数 - -第一种从 `main` 函数中调用 `clarinet` 函数的方式使用绝对路径。因为 `clarinet` 与 `main` 定义于同一 crate 中,我们使用 `crate` 关键字来开始绝对路径。接着包含每一个模块直到 `clarinet`。这类似于指定 `/sound/instrument/clarinet` 来运行电脑上这个位置的程序;使用 `crate` 从 crate 根开始就类似于在 shell 中使用 `/` 从文件系统根开始。 - -第二种从 `main` 函数中调用 `clarinet` 函数的方式使用相对路径。该路径以 `sound` 开始,它是定义于与 `main` 函数相同模块树级别的模块。这类似于指定 `sound/instrument/clarinet` 来运行电脑上这个位置的程序;以名称开头意味着路径是相对的。 - -示例 7-4 提到了它并不能编译,让我们尝试编译并看看为什么不行!示例 7-5 展示了错误。 - -```text -$ cargo build - Compiling sampleproject v0.1.0 (file:///projects/sampleproject) -error[E0603]: module `instrument` is private - --> src/main.rs:11:19 - | -11 | crate::sound::instrument::clarinet(); - | ^^^^^^^^^^ - -error[E0603]: module `instrument` is private - --> src/main.rs:14:12 - | -14 | sound::instrument::clarinet(); - | ^^^^^^^^^^ -``` - -示例 7-5: 构建示例 7-4 出现的编译器错误 - -错误信息说 `instrument` 模块是私有的。可以看到 `instrument` 模块和 `clarinet` 函数的路径是正确的,不过 Rust 不让我们使用,因为他们是私有的。现在是学习 `pub` 关键字的时候了! - -### 模块作为私有性的边界 - -之前我们讨论到模块的语法和组织代码的用途。Rust 采用模块还有另一个原因:模块是 Rust 中的 **私有性边界**(*privacy boundary*)。如果你希望函数或结构体是私有的,将其放入模块。私有性规则有如下: - -* 所有项(函数、方法、结构体、枚举、模块和常量)默认是私有的。 -* 可以使用 `pub` 关键字使项变为公有。 -* 不允许使用定义于当前模块的子模块中的私有代码。 -* 允许使用任何定义于父模块或当前模块中的代码。 - -换句话说,对于没有 `pub` 关键字的项,当你从当前模块向 “下” 看时是私有的,不过当你向 “上” 看时是公有的。再一次想象一下文件系统:如果你没有某个目录的权限,则无法从父目录中查看其内容。如果有该目录的权限,则可以查看其中的目录和任何父目录。 - -### 使用 `pub` 关键字使项变为公有 - -示例 7-5 中的错误说 `instrument` 模块是私有的。让我们使用 `pub` 关键字标记 `instrument` 模块使其可以在 `main` 函数中使用。这些改变如示例 7-6 所示,它仍然不能编译,不过会产生一个不同的错误: - -文件名: src/main.rs - -```rust,ignore,does_not_compile -mod sound { - pub mod instrument { - fn clarinet() { - // 函数体 - } - } -} - -fn main() { - // Absolute path - crate::sound::instrument::clarinet(); - - // Relative path - sound::instrument::clarinet(); -} -``` - -示例 7-6: 将 `instrument` 模块声明为 `pub` 以便可以在 `main` 中使用 - -在 `mod instrument` 之前增加 `pub` 关键字使得模块变为公有。通过这个改变如果允许访问 `sound` 的话,我们就可以访问 `instrument`。`instrument` 的内容仍然是私有的;使得模块公有并不使其内容也是公有的。模块上的 `pub` 关键字允许其父模块引用它。 - -不过示例 7-6 中的代码仍然产生错误,如示例 7-7 所示: - -```text -$ cargo build - Compiling sampleproject v0.1.0 (file:///projects/sampleproject) -error[E0603]: function `clarinet` is private - --> src/main.rs:11:31 - | -11 | crate::sound::instrument::clarinet(); - | ^^^^^^^^ - -error[E0603]: function `clarinet` is private - --> src/main.rs:14:24 - | -14 | sound::instrument::clarinet(); - | ^^^^^^^^ -``` - -示例 7-7: 构建示例 7-6 时产生的编译器错误 - -现在的错误表明 `clarinet` 函数是私有的。私有性规则适用于结构体、枚举、函数和方法以及模块。 - -在 `clarinet` 函数前增加 `pub` 关键字使其变为公有,如示例 7-8 所示: - -文件名: src/main.rs - -```rust -mod sound { - pub mod instrument { - pub fn clarinet() { - // 函数体 - } - } -} - -fn main() { - // 绝对路径 - crate::sound::instrument::clarinet(); - - // 相对路径 - sound::instrument::clarinet(); -} -``` - -示例 7-8: 在 `mod -instrument` 和 `fn clarinet` 之前都增加 `pub` 关键字使我们可以在 `main` 中调用此函数 - -现在可以编译了!让我们看看绝对路径和相对路径再次检查为什么增加 `pub` 关键字使得我们可以在 `main` 中调用这些路径。 - -在绝对路径的情况下,我们从 `crate`,也就是 crate 根开始。从这开始有 `sound`,这是一个定义于 crate 根中的模块。`sound` 模块不是公有的,不过因为 `main` 函数与 `sound` 定义于同一模块中,可以从 `main` 中引用 `sound`。接下来是 `instrument`,这个模块标记为 `pub`。我们可以访问 `instrument` 的父模块,所以可以访问 `instrument`。最后,`clarinet` 函数被标记为 `pub` 所以可以访问其父模块,所以这个函数调用是有效的! - -在相对路径的情况下,其逻辑与绝对路径相同,除了第一步。不同于从 crate 根开始,路径从 `sound` 开始。`sound` 模块与 `main` 定义于同一模块,所以从 `main` 所在模块开始定义的路径是有效的。接下来因为 `instrument` 和 `clarinet` 被标记为 `pub`,路径其余的部分也是有效的,因此函数调用也是有效的! - -### 使用 `super` 开始相对路径 - -也可以使用 `super` 开头来构建相对路径。这么做类似于文件系统中以 `..` 开头:该路径从 **父** 模块开始而不是当前模块。这在例如示例 7-9 这样的情况下有用处,在这里 `clarinet` 函数通过指定以 `super` 开头的路径调用 `breathe_in` 函数: - -文件名: src/lib.rs - -```rust -# fn main() {} -# -mod instrument { - fn clarinet() { - super::breathe_in(); - } -} - -fn breathe_in() { - // 函数体 -} -``` - -示例 7-9: 使用以 `super` 开头的路径从父目录开始调用函数 - -`clarinet` 函数位于 `instrument` 模块中,所以可以使用 `super` 进入 `instrument` 的父模块,也就是根 `crate`。从这里可以找到 `breathe_in`。成功! - -你可能想要使用 `super` 开头的相对路径而不是以 `crate` 开头的绝对路径的原因是 `super` 可能会使修改有着不同模块层级结构的代码变得更容易,如果定义项和调用项的代码被一同移动的话。例如,如果我们决定将 `instrument` 模块和 `breathe_in` 函数放入 `sound` 模块中,这时我们只需增加 `sound` 模块即可,如示例 7-10 所示。 - -文件名: src/lib.rs - -```rust -mod sound { - mod instrument { - fn clarinet() { - super::breathe_in(); - } - } - - fn breathe_in() { - // 函数体 - } -} -``` - -示例 7-10: 增加一个名为 `sound` 的父模块并不影响相对路径 `super::breathe_in` - -示例 7-10 在 `clarinet` 函数中调用 `super::breathe_in` 将如示例 7-9 一样继续有效,无需更新路径。如果在 `clarinet` 函数不使用 `super::breathe_in` 而是使用 `crate::breathe_in` 的话,当增加父模块 `sound` 后,则需要更新 `clarinet` 函数使用 `crate::sound::breathe_in` 路径。使用相对路径可能意味着重新布局模块时需要更少的必要修改。 - -### 对结构体和枚举使用 `pub` - -可以以模块与函数相同的方式来设计公有的结构体和枚举,不过有一些额外的细节。 - -如果在结构体定义中使用 `pub`,可以使结构体公有。然而结构体的字段仍是私有的。可以在每一个字段的基准上选择其是否公有。在示例 7-11 中定义了一个公有结构体 `plant::Vegetable`,其包含公有的 `name` 字段和私有的 `id` 字段。 - -文件名: src/main.rs - -```rust -mod plant { - pub struct Vegetable { - pub name: String, - id: i32, - } - - impl Vegetable { - pub fn new(name: &str) -> Vegetable { - Vegetable { - name: String::from(name), - id: 1, - } - } - } -} - -fn main() { - let mut v = plant::Vegetable::new("squash"); - - v.name = String::from("butternut squash"); - println!("{} are delicious", v.name); - - // 如果将如下行取消注释代码将无法编译: - // println!("The ID is {}", v.id); -} -``` - -示例 7-11: 结构体带有公有和私有的字段 - -因为 `plant::Vegetable` 结构体的 `name` 字段使公有的,在 `main` 中可以使用点号读写 `name` 字段。不允许在 `main` 中使用 `id` 字段因为其使私有的。尝试取消注释的行来打印 `id` 字段的值来看看会出现什么错误!另外注意因为 `plant::Vegetable` 有私有字段,需要提供一个公有的关联函数来构建 `Vegetable` 的实例(这里使用了传统的名称 `new`)。如果 `Vegetable` 没有提供这么一个函数,我们就不能在 `main` 中创建 `Vegetable` 的实例,因为在 `main` 中不允许设置私有字段 `id` 的值。 - -相反,如果有一个公有枚举,其所有成员都是公有。只需在 `enum` 关键词前加上 `pub`,如示例 7-12 所示。 - -文件名: src/main.rs - -```rust -mod menu { - pub enum Appetizer { - Soup, - Salad, - } -} - -fn main() { - let order1 = menu::Appetizer::Soup; - let order2 = menu::Appetizer::Salad; -} -``` - -示例 7-12: 将枚举设计为公有会使其所有成员公有 - -因为 `Appetizer` 枚举是公有的,可以在 `main` 中使用 `Soup` and `Salad` 成员。 - -还有一种使用 `pub` 的场景我们还没有涉及到,而这是我们最后要讲的模块功能:`use` 关键字。我们先单独介绍 `use`,然后展示如何结合使用 `pub` 和 `use`。 - -### 使用 `use` 关键字将名称引入作用域 - -你可能考虑过本章很多的函数调用的路径是冗长和重复的。例如示例 7-8 中,当我们选择 `clarinet` 函数的绝对或相对路径时,每次想要调用 `clarinet` 时都不得不也指定 `sound` 和 `instrument`。幸运的是,有一次性将路径引入作用域然后就像调用本地项那样的方法:使用 `use` 关键字。在示例 7-13 中将 `crate::sound::instrument` 模块引入了 `main` 函数的作用域,以便只需指定 `instrument::clarinet` 来调用 `clarinet` 函数。 - -文件名: src/main.rs - -```rust -mod sound { - pub mod instrument { - pub fn clarinet() { - // 函数体 - } - } -} - -use crate::sound::instrument; - -fn main() { - instrument::clarinet(); - instrument::clarinet(); - instrument::clarinet(); -} -``` - -示例 7-13: 使用 `use` 将模块引入作用域并使用绝对路径来缩短在模块中调用项所必须的路径 - -在作用域中增加 `use` 和路径类似于在文件系统中创建软连接(符号连接,symbolic link)。通过在 crate 根增加 `use crate::sound::instrument`,现在 `instrument` 在作用域中就是有效的名称了,如同它被定义于 crate 根一样。现在,既可以使用老的全路径方式取得 `instrument` 模块的项,也可以使用新的通过 `use` 创建的更短的路径。通过 `use` 引入作用域的路径也会检查私有性,同其它路径一样。 - -如果希望通过 `use` 和相对路径来将项引入作用域,则与直接通过相对路径调用项有些小的区别:不同于从当前作用域的名称开始,`use` 中的路径必须以 `self` 示例 7-14 展示了如何指定相对路径来取得与示例 7-13 中使用绝对路径一样的行为。 - -文件名: src/main.rs - -```rust -mod sound { - pub mod instrument { - pub fn clarinet() { - // 函数体 - } - } -} - -use self::sound::instrument; - -fn main() { - instrument::clarinet(); - instrument::clarinet(); - instrument::clarinet(); -} -``` - -示例 7-14: 通过 `use` 和相对路径将模块引入作用域 - -当指定 `use` 后以 `self` 开头的相对路径在未来可能不是必须的;这是一个开发者正在尽力消除的语言中的不一致。 - -如果调用项目的代码移动到模块树的不同位置但是定义项目的代码却没有,那么选择使用 `use` 指定绝对路径可以使更新更轻松,这与示例 7-10 中同时移动的情况相对。例如,如果我们决定采用示例 7-13 的代码,将 `main` 函数的行为提取到函数 `clarinet_trio` 中,并将该函数移动到模块 `performance_group` 中,这时 `use` 所指定的路径无需变化,如示例 7-15 所示。 - -文件名: src/main.rs - -```rust -mod sound { - pub mod instrument { - pub fn clarinet() { - // 函数体 - } - } -} - -mod performance_group { - use crate::sound::instrument; - - pub fn clarinet_trio() { - instrument::clarinet(); - instrument::clarinet(); - instrument::clarinet(); - } -} - -fn main() { - performance_group::clarinet_trio(); -} -``` - -示例 7-15: 移动调用项的代码时绝对路径无需移动 - -相反,如果对示例 7-14 中指定了相对路径的代码做同样的修改,则需要将 `use -self::sound::instrument` 变为 `use super::sound::instrument`。如果你不确定将来模块树会如何变化,那么选择采用相对或绝对路径是否会减少修改可能全靠猜测,不过本书的作者倾向于通过 `crate` 指定绝对路径,因为定义和调用项的代码更有可能相互独立的在模块树中移动,而不是像示例 7-10 那样一同移动。 - -### `use` 函数路径使用习惯 VS 其他项 - -示例 7-13 中,你可能会好奇为什么指定 `use crate::sound::instrument` 接着在 `main` 中调用 `instrument::clarinet`,而不是如示例 7-16 所示的有相同行为的代码: - -文件名: src/main.rs - -```rust -mod sound { - pub mod instrument { - pub fn clarinet() { - // 函数体 - } - } -} - -use crate::sound::instrument::clarinet; - -fn main() { - clarinet(); - clarinet(); - clarinet(); -} -``` - -示例 7-16: 通过 `use` 将 `clarinet` 函数引入作用域,这是不推荐的 - -对于函数来说,通过 `use` 指定函数的父模块接着指定父模块来调用方法被认为是习惯用法。这么做而不是像示例 7-16 那样通过 `use` 指定函数的路径,清楚的表明了函数不是本地定义的,同时仍最小化了指定全路径时的重复。 - -对于结构体、枚举和其它项,通过 `use` 指定项的全路径是习惯用法。例如,示例 7-17 展示了将标准库中 `HashMap` 结构体引入作用域的习惯用法。 - -文件名: src/main.rs - -```rust -use std::collections::HashMap; - -fn main() { - let mut map = HashMap::new(); - map.insert(1, 2); -} -``` - -示例 7-17: 将 `HashMap` 引入作用域的习惯用法 - -相反,示例 7-18 中的代码将 `HashMap` 的父模块引入作用域不被认为是习惯用法。这个习惯并没有很强制的理由;这是慢慢形成的习惯同时人们习惯于这么读写。 - -文件名: src/main.rs - -```rust -use std::collections; - -fn main() { - let mut map = collections::HashMap::new(); - map.insert(1, 2); -} -``` - -示例 7-18: 将 `HashMap` 引入作用域的非习惯方法 - -这个习惯的一个例外是如果 `use` 语句会将两个同名的项引入作用域时,这是不允许的。示例 7-19 展示了如何将两个不同父模块的 `Result` 类型引入作用域并引用它们。 - -文件名: src/lib.rs - -```rust -use std::fmt; -use std::io; - -fn function1() -> fmt::Result { -# Ok(()) -} -fn function2() -> io::Result<()> { -# Ok(()) -} -``` - -示例 7-19: 将两个同名类型引入作用域必须使用父模块 - -因为如果我们指定 `use std::fmt::Result` 和 `use std::io::Result`,则作用域中会有两个 `Result` 类型,Rust 无法知道我们想用哪个 `Result`。尝试这么做并看看编译器错误! - -### 通过 `as` 关键字重命名引入作用域的类型 - -将两个同名类型引入同一作用域这个问题还有另一个解决办法:可以通过在 `use` 后加上 `as` 和一个新名称来为此类型指定一个新的本地名称。示例 7-20 展示了另一个编写示例 7-19 中代码的方法,通过 `as` 重命名了其中一个 `Result` 类型。 - -文件名: src/lib.rs - -```rust -use std::fmt::Result; -use std::io::Result as IoResult; - -fn function1() -> Result { -# Ok(()) -} -fn function2() -> IoResult<()> { -# Ok(()) -} -``` - -示例 7-20: 通过 `as` 关键字重命名引入作用域的类型 - -在第二个 `use` 语句中,我们选择 `IoResult` 作为 `std::io::Result` 的新名称,它与从 `std::fmt` 引入作用域的 `Result` 并不冲突。这样做也被认为是惯用的;示例 7-19 还是示例 7-20 全看你的选择。 - -### 通过 `pub use` 重导出名称 - -当使用 `use` 关键字将名称导入作用域时,在新作用域中可用的名称是私有的。如果希望调用你编写的代码的代码能够像你一样在其自己的作用域内引用这些类型,可以结合 `pub` 和 `use`。这个技术被称为 “重导出”(*re-exporting*),因为这样做将项引入作用域并同时使其可供其他代码引入自己的作用域。 - -例如,示例 7-21 展示了示例 7-15 中的代码将 `performance_group` 的 `use` 变为 `pub use` 的版本。 - -文件名: src/main.rs - -```rust -mod sound { - pub mod instrument { - pub fn clarinet() { - // 函数体 - } - } -} - -mod performance_group { - pub use crate::sound::instrument; - - pub fn clarinet_trio() { - instrument::clarinet(); - instrument::clarinet(); - instrument::clarinet(); - } -} - -fn main() { - performance_group::clarinet_trio(); - performance_group::instrument::clarinet(); -} -``` - -示例 7-21: 通过 `pub use` 使名称可引入任何代码的作用域中 - -通过 `pub use`,现在 `main` 函数可以通过新路径 `performance_group::instrument::clarinet` 来调用 `clarinet` 函数。如果没有指定 `pub use`,`clarinet_trio` 函数可以在其作用域中调用 `instrument::clarinet` 但 `main` 则不允许使用这个新路径。 - -### 使用外部包 - -在第二章中我们编写了一个猜猜看游戏。那个项目使用了一个外部包,`rand`,来生成随机数。为了在项目中使用 `rand`,在 *Cargo.toml* 中加入了如下行: - -文件名: Cargo.toml - -```toml -[dependencies] -rand = "0.5.5" -``` - -在 *Cargo.toml* 中加入 `rand` 依赖告诉了 Cargo 要从 *https://crates.io* 下载 `rand` 和其依赖,并使其可在项目代码中使用。 - -接着,为了将 `rand` 定义引入项目包的作用域,加入一行 `use`,它以 `rand` 包名开头并列出了需要引入作用域的项。回忆一下第二章的 “生成一个随机数” 部分,我们曾将 `Rng` trait 引入作用域并调用了 `rand::thread_rng` 函数: - -```rust,ignore -use rand::Rng; - -fn main() { - let secret_number = rand::thread_rng().gen_range(1, 101); -} -``` - -*https://crates.io* 上有很多社区成员发布的包,将其引入你自己的项目涉及到相同的步骤:在 *Cargo.toml* 列出它们并通过 `use` 将其中定义的项引入项目包的作用域中。 - -注意标准库(`std`)对于你的包来说也是外部 crate。因为标准库随 Rust 语言一同分发,无需修改 *Cargo.toml* 来引入 `std`,不过需要通过 `use` 将标准库中定义的项引入项目包的作用域中来引用它们,比如 `HashMap`: - -```rust -use std::collections::HashMap; -``` - -这是一个以标准库 crate 名 `std` 开头的绝对路径。 - -### 嵌套路径来消除大量的 `use` 行 - -当需要引入很多定义于相同包或相同模块的项时,为每一项单独列出一行会占用源码很大的空间。例如猜猜看章节示例 2-4 中有两行 `use` 语句都从 `std` 引入项到作用域: - -文件名: src/main.rs - -```rust -use std::cmp::Ordering; -use std::io; -// ---snip--- -``` - -可以使用嵌套的路径将同样的项在一行中引入而不是两行,这么做需要指定路径的相同部分,接着是两个冒号,接着是大括号中的各自不同的路径部分,如示例 7-22 所示。 - -文件名: src/main.rs - -```rust -use std::{cmp::Ordering, io}; -// ---snip--- -``` - -示例 7-22: 指定嵌套的路径在一行中将多个带有相同前缀的项引入作用域 - -在从相同包或模块中引入很多项的程序中,使用嵌套路径显著减少所需的单独 `use` 语句! - -也可以剔除掉完全包含在另一个路径中的路径。例如,示例 7-23 中展示了两个 `use` 语句:一个将 `std::io` 引入作用域,另一个将 `std::io::Write` 引入作用域: - -文件名: src/lib.rs - -```rust -use std::io; -use std::io::Write; -``` - -示例 7-23: 通过两行 `use` 语句引入两个路径,其中一个是另一个的子路径 - -两个路径的相同部分是 `std::io`,这正是第一个路径。为了在一行 `use` 语句中引入这两个路径,可以在嵌套路径中使用 `self`,如示例 7-24 所示。 - -文件名: src/lib.rs - -```rust -use std::io::{self, Write}; -``` - -示例 7-24: 将示例 7-23 中部分重复的路径合并为一个 `use` 语句 - -这将 `std::io` 和 `std::io::Write` 同时引入作用域。 - -### 通过 glob 运算符将所有的公有定义引入作用域 - -如果希望将一个路径下 **所有** 公有项引入作用域,可以指定路径后跟 `*`,glob 运算符: - -```rust -use std::collections::*; -``` - -这个 `use` 语句将 `std::collections` 中定义的所有公有项引入当前作用域。 - -使用 glob 运算符时请多加小心!如此难以推导作用域中有什么名称和它们是在何处定义的。 - -glob 运算符经常用于测试模块 `tests` 中,这时会将所有内容引入作用域;我们将在第十一章 “如何编写测试” 部分讲解。glob 运算符有时也用于 prelude 模式;查看 [标准库中的文档](https://doc.rust-lang.org/std/prelude/index.html#other-preludes) 了解这个模式的更多细节。 - -### 将模块分割进不同文件 - -目前本章所有的例子都在一个文件中定义多个模块。当模块变得更大时,你可能想要将它们的定义移动到一个单独的文件中使代码更容易阅读。 - -例如从示例 7-8 的代码开始,我们可以通过修改 crate 根文件(这里是 *src/main.rs*)将 `sound` 模块移动到其自己的文件 *src/sound.rs* 中,如示例 7-25 所示。 - -文件名: src/main.rs - -```rust,ignore -mod sound; - -fn main() { - // 绝对路径 - crate::sound::instrument::clarinet(); - - // 相对路径 - sound::instrument::clarinet(); -} -``` - -示例 7-25: 声明 `sound` 模块,其内容位于 *src/sound.rs* 文件 - -而 *src/sound.rs* 中会包含 `sound` 模块的内容,如示例 7-26 所示。 - -文件名: src/sound.rs - -```rust -pub mod instrument { - pub fn clarinet() { - // 函数体 - } -} -``` - -示例 7-26: `sound` 模块中的定义位于 *src/sound.rs* 中 - -在 `mod sound` 后使用分号而不是代码块告诉 Rust 在另一个与模块同名的文件中加载模块的内容。 - -继续重构我们例子,将 `instrument` 模块也提取到其自己的文件中,修改 *src/sound.rs* 只包含 `instrument` 模块的声明: - -文件名: src/sound.rs - -```rust -pub mod instrument; -``` - -接着创建 *src/sound* 目录和 *src/sound/instrument.rs* 文件来包含 `instrument` 模块的定义: - -文件名: src/sound/instrument.rs - -```rust -pub fn clarinet() { - // 函数体 -} -``` - -模块树依然保持相同,`main` 中的函数调用也无需修改继续保持有效,即使其定义存在于不同的文件中。这样随着代码增长可以将模块移动到新文件中。 - -## 总结 - -Rust 提供了将包组织进 crate、将 crate 组织进模块和通过指定绝对或相对路径从一个模块引用另一个模块中定义的项的方式。可以通过 `use` 语句将路径引入作用域,这样在多次使用时可以使用更短的路径。模块定义的代码默认是私有的,不过可以选择增加 `pub` 关键字使其定义变为公有。 - -接下来,让我们看看一些标准库提供的集合数据类型,你可以利用它们编写出漂亮整洁的代码。 diff --git a/src/ch07-03-importing-names-with-use.md b/src/ch07-03-importing-names-with-use.md deleted file mode 100644 index 719089f..0000000 --- a/src/ch07-03-importing-names-with-use.md +++ /dev/null @@ -1,224 +0,0 @@ -## 引用不同模块中的名称 - -> [ch07-03-importing-names-with-use.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch07-03-importing-names-with-use.md) ->
-> commit 550c8ea6f74060ff1f7b67e7e1878c4da121682d - -我们已经讲到了如何使用模块名称作为调用的一部分,来调用模块中的函数,如示例 7-7 中所示的 `nested_modules` 函数调用。 - -文件名: src/main.rs - -```rust -pub mod a { - pub mod series { - pub mod of { - pub fn nested_modules() {} - } - } -} - -fn main() { - a::series::of::nested_modules(); -} -``` - -示例 7-7:通过完全指定模块中的路径来调用函数 - -如你所见,指定函数的完全限定名称可能会非常冗长。所幸 Rust 有一个关键字使得这些调用显得更简洁。 - -### 使用 `use` 关键字将名称导入作用域 - -Rust 的 `use` 关键字能将你想要调用的函数所在的模块引入到当前作用域中,通过这种方式可以缩短冗长的函数调用。这是一个将 `a::series::of` 模块导入一个二进制 crate 的根作用域的例子: - -文件名: src/main.rs - -```rust -pub mod a { - pub mod series { - pub mod of { - pub fn nested_modules() {} - } - } -} - -use a::series::of; - -fn main() { - of::nested_modules(); -} -``` - -`use a::series::of;` 这一行的意思是每当想要引用 `of` 模块时,不必使用完整的 `a::series::of` 路径,可以直接使用 `of`。 - -`use` 关键字只将指定的模块引入作用域;它并不会将其子模块也引入。这就是为什么想要调用 `nested_modules` 函数时仍然必须写成 `of::nested_modules`。 - -也可以将函数本身引入到作用域中,通过如下在 `use` 中指定函数的方式: - -```rust -pub mod a { - pub mod series { - pub mod of { - pub fn nested_modules() {} - } - } -} - -use a::series::of::nested_modules; - -fn main() { - nested_modules(); -} -``` - -这使得我们可以忽略所有的模块并直接引用函数。 - -因为枚举也像模块一样组成了某种命名空间,也可以使用 `use` 来导入枚举的成员。对于任何类型的 `use` 语句,如果从一个命名空间导入多个项,可以在最后使用大括号和逗号来列举它们,像这样: - -```rust -enum TrafficLight { - Red, - Yellow, - Green, -} - -use TrafficLight::{Red, Yellow}; - -fn main() { - let red = Red; - let yellow = Yellow; - let green = TrafficLight::Green; -} -``` - -我们仍然为 `Green` 成员指定了 `TrafficLight` 命名空间,因为并没有在 `use` 语句中包含 `Green`。 - -### 使用 glob 将所有名称引入作用域 - -为了一次将某个命名空间下的所有名称都引入作用域,可以使用 `*` 语法,这称为 **glob 运算符**(*glob operator*)。这个例子将一个枚举的所有成员引入作用域而没有将其一一列举出来: - -```rust -enum TrafficLight { - Red, - Yellow, - Green, -} - -use TrafficLight::*; - -fn main() { - let red = Red; - let yellow = Yellow; - let green = Green; -} -``` - -`*` 会将 `TrafficLight` 命名空间中所有可见的项都引入作用域。请保守的使用 glob:它们是方便的,但是也可能会引入多于预期的内容从而导致命名冲突。 - -### 使用 `super` 访问父模块 - -正如我们已经知道的,当创建一个库 crate 时,Cargo 会生成一个 `tests` 模块。现在让我们来深入了解一下。在 `communicator` 项目中,打开 *src/lib.rs*。 - -文件名: src/lib.rs - -```rust,ignore -pub mod client; - -pub mod network; - -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - assert_eq!(2 + 2, 4); - } -} -``` - -第十一章会更详细的解释测试,不过其中部分内容现在应该可以理解了:有一个叫做 `tests` 的模块紧邻其他模块,同时包含一个叫做 `it_works` 的函数。即便存在一些特殊注解,`tests` 也不过是另外一个模块!所以我们的模块层次结构看起来像这样: - -```text -communicator - ├── client - ├── network - | └── client - └── tests -``` - -测试是为了检验库中的代码而存在的,所以让我们尝试在 `it_works` 函数中调用 `client::connect` 函数,即便现在不准备测试任何功能。这还不能工作: - -文件名: src/lib.rs - -```rust -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - client::connect(); - } -} -``` - -使用 `cargo test` 命令运行测试: - -```text -$ cargo test - Compiling communicator v0.1.0 (file:///projects/communicator) -error[E0433]: failed to resolve. Use of undeclared type or module `client` - --> src/lib.rs:9:9 - | -9 | client::connect(); - | ^^^^^^ Use of undeclared type or module `client` -``` - -编译失败了,不过为什么呢?并不需要像 *src/main.rs* 那样将 `communicator::` 置于函数前,因为这里肯定是在 `communicator` 库 crate 之内的。失败的原因是路径是相对于当前模块的,在这里就是 `tests`。唯一的例外就是 `use` 语句,它默认是相对于 crate 根模块的。我们的 `tests` 模块需要 `client` 模块位于其作用域中! - -那么如何在模块层次结构中回退一级模块,以便在 `tests` 模块中能够调用 `client::connect`函数呢?在 `tests` 模块中,要么可以在开头使用双冒号来让 Rust 知道我们想要从根模块开始并列出整个路径: - -```rust,ignore -::client::connect(); -``` - -要么可以使用 `super` 在层级中上移到当前模块的上一级模块,如下: - -```rust,ignore -super::client::connect(); -``` - -在这个例子中这两个选择看不出有多么大的区别,不过随着模块层次的更加深入,每次都从根模块开始就会显得很长了。在这些情况下,使用 `super` 来获取当前模块的同级模块是一个好的捷径。再加上,如果在代码中的很多地方指定了从根开始的路径,那么当通过移动子树或到其他位置来重新排列模块时,最终就需要更新很多地方的路径,这就非常乏味无趣了。 - -在每一个测试中总是不得不编写 `super::` 也会显得很恼人,不过你已经见过解决这个问题的利器了:`use`!`super::` 的功能改变了提供给 `use` 的路径,使其不再相对于根模块而是相对于父模块。 - -为此,特别是在 `tests` 模块,`use super::something` 是常用的手段。所以现在的测试看起来像这样: - -文件名: src/lib.rs - -```rust -#[cfg(test)] -mod tests { - use super::client; - - #[test] - fn it_works() { - client::connect(); - } -} -``` - -如果再次运行`cargo test`,测试将会通过而且测试结果输出的第一部分将会是: - -```text -$ cargo test - Compiling communicator v0.1.0 (file:///projects/communicator) - Running target/debug/communicator-92007ddb5330fa5a - -running 1 test -test tests::it_works ... ok - -test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out -``` - -## 总结 - -现在你掌握了组织代码的核心科技!利用它们将相关的代码组合在一起、防止代码文件过长并将一个整洁的公有 API 展现给库的用户。 - -接下来,让我们看看一些标准库提供的集合数据类型,你可以利用它们编写出漂亮整洁的代码。 diff --git a/src/ch07-04-bringing-paths-into-scope-with-the-use-keyword.md b/src/ch07-04-bringing-paths-into-scope-with-the-use-keyword.md index 4a2c6a7..3ac3070 100644 --- a/src/ch07-04-bringing-paths-into-scope-with-the-use-keyword.md +++ b/src/ch07-04-bringing-paths-into-scope-with-the-use-keyword.md @@ -1,6 +1,6 @@ ## 使用 `use` 关键字将名称引入作用域 -> [使用 `use` 关键字将名称引入作用域](https://github.com/rust-lang/book/blob/master/src/ch07-04-bringing-paths-into-scope-with-the-use-keyword.md) +> [ch07-04-bringing-paths-into-scope-with-the-use-keyword.md](https://github.com/rust-lang/book/blob/master/src/ch07-04-bringing-paths-into-scope-with-the-use-keyword.md) >
> commit 6d3e76820418f2d2bb203233c61d90390b5690f1