Rust 模块系统
相信不少人和我一样初次接触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
是我创建的项目的名字。或者你也可以通过之前讨论的方式调用。
基本这些就是常见的导入情况了,如果还有遗漏的或者不懂的导入情景可以在评论区补充。