将模块分离到不同的文件中

到目前为止,本章中的所有示例都在一个文件中定义了多个模块。 当模块变大时,您可能希望将它们的定义移到单独的文件中,以使代码更容易导航。

例如,让我们从列表 7-17 中的代码开始,该代码有多个 restaurant 模块。我们将把模块提取到文件中,而不是在 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 文件之前,这将无法编译。

Filename: src/lib.rs
mod front_of_house;

pub use crate::front_of_house::hosting;

pub fn eat_at_restaurant() {
    hosting::add_to_waitlist();
}
Listing 7-21: Declaring the front_of_house module whose body will be in src/front_of_house.rs

接下来,将大括号中的代码放入一个名为src/front_of_house.rs的新文件中,如清单7-22所示。编译器知道在该文件中查找是因为它在crate根目录中遇到了名为front_of_house的模块声明。

Filename: src/front_of_house.rs
pub mod hosting {
    pub fn add_to_waitlist() {}
}
Listing 7-22: Definitions inside the front_of_house module in src/front_of_house.rs

请注意,您只需要在模块树中一次使用mod声明来加载文件。一旦编译器知道文件是项目的一部分(并且知道代码在模块树中的位置,因为您放置了mod语句的位置),项目中的其他文件应该使用声明该文件时的路径来引用其代码,如“模块树中引用项的路径”部分所述。换句话说,mod并不是您可能在其他编程语言中见过的“包含”操作。

接下来,我们将把hosting模块提取到它自己的文件中。这个过程有点不同,因为hostingfront_of_house的子模块,而不是根模块的子模块。我们将把hosting的文件放在一个新目录中,该目录将以模块树中的祖先命名,在这种情况下为src/front_of_house

要开始移动hosting,我们更改src/front_of_house.rs以仅包含hosting模块的声明:

Filename: src/front_of_house.rs
pub mod hosting;

然后我们创建一个 src/front_of_house 目录和一个 hosting.rs 文件来 包含在 hosting 模块中定义的内容:

Filename: src/front_of_house/hosting.rs
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 (较旧的风格,仍然支持的路径)

对于名为 hosting 的模块,它是 front_of_house 的子模块,编译器将在以下位置查找模块的代码:

  • 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 关键字来使定义公开。

在下一章中,我们将介绍标准库中的一些集合数据结构,您可以在组织良好的代码中使用这些结构。