相信不少人和我一样初次接触Rust的模块系统时都会觉得难以理解,因为与之前学的编程语言的文件导入不一样。个人觉得C/C++系的导入是最容易理解的,直接将头文件中定义的内容include后就可以使用其中的内容。

但是Rust不一样,Rust使用模块 mod 来管理所有的权限和导入问题。在创建了一个微型项目测试一会之后,并结合这篇文章,我总算搞懂了Rust的模块系统。

当导入文件在同一个文件夹下时

首先使用 Cargo 创建一个新项目,我这里使用官网教程使用的案例:一个餐厅。一个餐厅分为前后端,前端负责点菜,后端负责做菜。这时项目还不复杂,因此我们直接用两个文件: front.rs 和 back.rs 来代表前后端。

如果我们想要在 main.rs 使用这两个文件夹中,我们需要创建一个模块,比如想要使用 front.rs 的点菜函数 order

mod front;

fn main() {
    front::order();
}

这里注意 front.rs 中的 order 必须是 pub 的。

你可能会奇怪,为什么 mod front 是写在 main.rs 文件里面的?毕竟按照官网教程的话,front 模块下的所有代码都应定义在 mod front {} 这个括号里面。但是那样的话 front 模块将只能在 front.rs 中使用,而无法导入到其它文件中。或者说需要再在其它文件中另外定义一个包含 front.rs 所有代码的模块,但那是另外一个模块,即便两个模块名字都是 front. 因为编译器不会去推测你的 front.rs 中到底是只有一个 mod front {} 还是有其它代码。

导入文件夹下的模块

很多时候用于表示某个功能的代码可能分散到多个文件中,而将这些文件收集到一个文件夹下是一种常见的操作。比如我们将餐厅的后端分散到多个文件中,因为后端包含多个步骤:洗菜,做菜,洗碗等。

要想让一个文件夹下的所有文件成为一个模块,首先这个文件夹中必须包含一个 mod.rs 文件,类似 node.js 中的 index.js 文件。没有这个文件,即便你在 main.rs 中使用 mod 声明模块编译器也无法找到。

其次,如果你只想在 mod.rs 中使用其它文件的代码的话,只需要 mod 声明这个模块后即可,就想我们在根目录下做的那样。而如果像高一级路径下使用,必须要经 mod.rs 通过 pub 声明后使用。

比如在 back 目录下的 wash.rs 表示洗菜,里面有一个洗菜函数 wash. 则在 main.rs 可以这般调用:

mod back;

fn main() {
    back::wash::wash();
}

在 back/mod.rs 中声明模块:

pub mod wash;

在back/wash.rs 声明函数:

pub fn wash() {
    println!("washing food.");
}

注意必须是声明为 pub

调用上一级路径下的文件夹下的模块

问题又来了,如果我们想要在 front 文件夹下使用 back 文件夹下的代码怎么办呢?

这时我们就要使用绝对路径,而且光是绝对路径还不行。必须要在 跟路径下的 lib.rs 文件中公共声明要使用 module, 所以我们声明:

pub mod back;

这样我们就在跟路径下创建了模块 back, 而跟路径下的所有模块都可以被 crate:: 来引用,这就是绝对路径。

为了避免麻烦,我们可以将这个模块直接带到front中。

在 front.rs 中:

use crate::back;

但值得注意的问题是,你不能在根目录下这样使用,不管是对于 main.rs 还是 根目录下的其他文件。比如,在根目录下如果你想要通过 crate::back::wash::wash() 是无法成功调用的。似乎是因为 crate 只对下一级目录可见。Instead, 你必须通过 hello::back::wash::wash(), 其中 hello 是我创建的项目的名字。或者你也可以通过之前讨论的方式调用。

基本这些就是常见的导入情况了,如果还有遗漏的或者不懂的导入情景可以在评论区补充。

WELCOME TO THE MACHINE