将模块拆分到不同文件中
到目前为止,本章中的所有示例都在一个文件中定义了多个模块。当模块变得很大时,你可能会想把它们的定义移到单独的文件中,以便更轻松地浏览代码。
例如,让我们从示例 7-17 中包含多个餐厅模块的代码开始。我们将把模块提取到不同文件中,而不是把所有模块都定义在 crate 根文件中。这里的 crate 根文件是 src/lib.rs,不过这个过程也适用于 crate 根文件为 src/main.rs 的二进制 crate。
首先,我们将 front_of_house 模块提取到它自己的文件中。删除 front_of_house 模块花括号内的代码,只留下 mod front_of_house; 声明,使 src/lib.rs 包含示例 7-21 所示的代码。请注意,在我们创建示例 7-22 中的 src/front_of_house.rs 文件之前,这段代码无法编译。
mod front_of_house;
pub use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
front_of_house 模块,其主体将位于 src/front_of_house.rs 中接下来,如示例 7-22 所示,将原先花括号中的代码放入名为 src/front_of_house.rs 的新文件中。编译器会查找这个文件,因为它在 crate 根中遇到了名为 front_of_house 的模块声明。
pub mod hosting {
pub fn add_to_waitlist() {}
}
front_of_house 模块内的定义请注意,在模块树中,你只需使用 mod 声明加载文件一次。编译器知道该文件属于项目(并根据你放置 mod 语句的位置,知道代码在模块树中的位置)后,项目中的其他文件应使用该文件声明位置的路径来引用其中的代码,正如“在模块树中引用项目的路径”一节所述。换句话说,mod 并不是某些其他编程语言中可能见过的“include”操作。
接下来,我们将 hosting 模块提取到它自己的文件中。这个过程略有不同,因为 hosting 是 front_of_house 的子模块,而不是根模块的子模块。我们会把 hosting 对应的文件放在一个新目录中,该目录按模块树中的祖先命名;在这个例子中,目录名为 src/front_of_house。
要开始移动 hosting,我们修改 src/front_of_house.rs,使其只包含 hosting 模块的声明:
pub mod hosting;
然后,我们创建 src/front_of_house 目录和 hosting.rs 文件,用来存放 hosting 模块中的定义:
pub fn add_to_waitlist() {}
如果我们把 hosting.rs 放在 src 目录中,编译器就会认为 hosting.rs 中的代码属于在 crate 根中声明的 hosting 模块,而不是 front_of_house 模块的子模块。编译器关于到哪些文件中查找各模块代码的规则,使目录和文件能够更贴合模块树的结构。
其他文件路径形式
到目前为止,我们介绍了 Rust 编译器使用的最惯用文件路径,但 Rust 也支持一种较旧的文件路径形式。对于在 crate 根中声明的 front_of_house 模块,编译器会在以下位置查找模块代码:
- src/front_of_house.rs(前文介绍的形式)
- src/front_of_house/mod.rs(较旧的形式,仍受支持)
对于作为 front_of_house 子模块的 hosting 模块,编译器会在以下位置查找模块代码:
- src/front_of_house/hosting.rs(前文介绍的形式)
- src/front_of_house/hosting/mod.rs(较旧的形式,仍受支持)
如果对同一个模块同时使用这两种形式,就会导致编译错误。在同一个项目中对不同模块混用这两种形式是允许的,但可能会让浏览项目的人感到困惑。
使用名为 mod.rs 的文件这种形式,主要缺点是项目中可能会出现许多同名的 mod.rs 文件;当你同时在编辑器中打开它们时,就容易混淆。
我们已将每个模块的代码移到了单独的文件中,而模块树保持不变。即使定义位于不同文件中,eat_at_restaurant 中的函数调用也无需修改即可正常工作。这种技巧让你可以随着模块变大,将它们移到新文件中。
请注意,src/lib.rs 中的 pub use crate::front_of_house::hosting 语句也没有变化,use 也不会影响作为 crate 一部分进行编译的文件。mod 关键字用于声明模块,而 Rust 会在与模块同名的文件中查找该模块的代码。
总结
Rust 允许你将一个包拆分成多个 crate,再将 crate 拆分成模块,从而可以在一个模块中引用另一个模块所定义的项目。你可以通过指定绝对路径或相对路径来实现这一点。使用 use 语句可以将这些路径引入作用域,这样在该作用域中多次使用某个项目时,就能采用更短的路径。模块代码默认是私有的,但你可以添加 pub 关键字来公开定义。
下一章中,我们将介绍标准库中的一些集合数据结构,你可以在组织良好的代码中使用它们。