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

到目前为止,本章中的所有示例都在一个文件中定义了多个模块。当模块变得很大时,您可能希望将其定义移动到单独的文件中,以便更容易浏览代码。

例如,让我们从代码清单 7-17 中包含多个餐厅模块的代码开始。我们将模块提取到文件中,而不是将所有模块都定义在 crate 根文件中。在这种情况下,crate 根文件是 src/lib.rs,但此过程也适用于二进制 crate,其 crate 根文件是 src/main.rs

首先,我们将 front_of_house 模块提取到它自己的文件中。删除 front_of_house 模块的花括号内的代码,只留下 mod front_of_house; 声明,以便 src/lib.rs 包含代码清单 7-21 中所示的代码。请注意,在我们创建代码清单 7-22 中的 src/front_of_house.rs 文件之前,这不会编译。

文件名:src/lib.rs

mod front_of_house;

pub use crate::front_of_house::hosting;

pub fn eat_at_restaurant() {
    hosting::add_to_waitlist();
}

代码清单 7-21:声明 front_of_house 模块,其主体将在 src/front_of_house.rs

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

文件名:src/front_of_house.rs

pub mod hosting {
    pub fn add_to_waitlist() {}
}

代码清单 7-22:src/front_of_house.rsfront_of_house 模块内的定义

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

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

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

文件名:src/front_of_house.rs

pub mod hosting;

然后,我们创建一个 src/front_of_house 目录和一个文件 hosting.rs,以包含在 hosting 模块中进行的定义

文件名: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 关键字使定义公开。

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