在这篇文章中,我将使用实际示例来解释模块系统,以便您清楚地了解它的工作原理,并可以立即开始在您的项目中应用它。
由于 Rust 的模块系统非常独特,我要求读者以开放的心态阅读这篇文章,并拒绝将其与其他语言中的模块工作方式进行比较。
让我们使用这个文件结构来模拟一个真实世界的项目:
my_project ├── Cargo.toml └─┬ src ├── main.rs ├── config.rs ├─┬ routes │ ├── health_route.rs │ └── user_route.rs └─┬ models └── user_model.rs
|
这些是我们应该能够使用我们的模块的不同方式:

这 3 个例子应该足以解释 Rust 的模块系统是如何工作的。
示例 1
让我们从第一个示例开始 - 导入config.rs.main.rs
// main.rs fn main() { println!("main"); } // config.rs fn print_config() { println!("config"); }
|
每个人犯的第一个错误只是因为我们有文件config.rs,health_route.rs等等,我们认为这些文件是modules,我们可以从其他文件中导入它们。
这是我们看到的(文件系统树)和编译器看到的(模块树):

令人惊讶的是,编译器只看到crate模块,也就是我们的main.rs文件。这是因为我们需要在Rust中显式地建立模块树--文件系统树和模块树之间没有隐式映射。
我们需要在Rust中明确建立模块树,默认是没有与文件系统的隐式映射。
要将一个文件添加到模块树中,我们需要使用mod关键字将该文件声明为一个子模块。
接下来让人困惑的是,你会认为我们只需在同一个文件中声明一个文件为模块,实际需要在另外一个文件中声明这个文件为模块!
由于我们在模块树中只有main.rs,让我们把config.rs声明为main.rs中的一个子模块。
mod 关键字声明了一个子模块
mod关键字的语法是这样的:
这里,编译器会在同一目录下寻找my_module.rs或my_module/mod.rs:
my_project ├── Cargo.toml └─┬ src ├── main.rs └── my_module.rs
or
my_project ├── Cargo.toml └─┬ src ├── main.rs └─┬ my_module └── mod.rs
|
由于main.rs和config.rs在同一个目录下,让我们把config模块声明如下:
// main.rs <strong>mod config;</strong>
fn main() { <strong>config::print_config();</strong> println!("main"); }
|
// config.rs fn print_config() { println!("config"); }
|
我们现在可以使用:: 语法来访问print_config函数。
这是模块树的样子:

我们已经成功声明了config模块!
但这还不足以调用config.rs里面的函数print_config。
默认情况下,Rust 中几乎所有内容都是私有的,我们需要使用pub关键字将函数设为 public:
// main.rs mod config;
fn main() { config::print_config(); println!("main"); }
// config.rs //- fn print_config() { <strong> pub fn print_config() {</strong> println!("config"); }
|
现在,这起作用了。我们已经成功地调用了一个定义在不同文件中的函数
例2
让我们试着从main.rs调用routes/health_route.rs中定义的print_health_route函数。
// main.rs mod config;
fn main() { config::print_config(); println!("main"); } // routes/health_route.rs fn print_health_route() { println!("health_route"); }
|
正如我们前面讨论的,我们只能对同一目录下的my_module.rs或my_module/mod.rs使用mod关键字。
因此,为了从main.rs调用routes/health_route.rs里面的函数,我们需要做以下事情:
- 创建一个名为routes/mod.rs的文件,在main.rs中声明routes子模块
- 在routes/mod.rs中声明health_route子模块并使其公开
- 将health_route.rs中的函数公开
my_project ├── Cargo.toml └─┬ src ├── main.rs ├── config.rs ├─┬ routes + │ ├── mod.rs //目录下有一个规定的mod.rs文件表示当前是模块 │ ├── health_route.rs │ └── user_route.rs └─┬ models └── user_model.rs // main.rs mod config; + mod routes;//声明routes模块
fn main() { + routes::health_route::print_health_route();// 使用routes模块 config::print_config(); println!("main"); } // routes/mod.rs routes模块文件中需要增加pub关键字 + pub mod health_route; // routes/health_route.rs - fn print_health_route() { + pub fn print_health_route() { println!("health_route"); }
|
这是模块树的样子:

我们现在可以调用在文件夹内的文件中定义的函数。
示例 3
让我们尝试main.rs => routes/user_route.rs => models/user_model.rs
// main.rs mod config; mod routes;
fn main() { routes::health_route::print_health_route(); config::print_config(); println!("main"); } // routes/user_route.rs fn print_user_route() { println!("user_route"); } // models/user_model.rs fn print_user_model() { println!("user_model"); }
|
我们想从main的print_user_route中调用函数print_user_model。
让我们做和以前一样的修改:
- 声明子模块;
- 使函数公开;
- 添加mod.rs文件。
my_project ├── Cargo.toml └─┬ src ├── main.rs ├── config.rs ├─┬ routes │ ├── mod.rs │ ├── health_route.rs │ └── user_route.rs └─┬ models + ├── mod.rs └── user_model.rs // main.rs mod config; mod routes; + mod models;
fn main() { routes::health_route::print_health_route(); + routes::user_route::print_user_route(); config::print_config(); println!("main"); } // routes/mod.rs pub mod health_route; + pub mod user_route; // routes/user_route.rs - fn print_user_route() { + pub fn print_user_route() { println!("user_route"); } // models/mod.rs + pub mod user_model; // models/user_model.rs - fn print_user_model() { + pub fn print_user_model() { println!("user_model"); }
|
这是模块树的样子:

等等,我们实际上还没有从print_user_route中调用print_user_model!
到目前为止,我们只从main.rs中调用了其他模块中定义的函数,
我们如何从其他文件中做到这一点?
如果我们看一下我们的模块树,print_user_model函数位于crate::models::user_model路径中。
所以:
为了在非main.rs的文件中使用一个模块,我们应该从模块树中到达该模块的必要路径来考虑。
// routes/user_route.rs pub fn print_user_route() { + crate::models::user_model::print_user_model(); println!("user_route"); }
|
我们已经成功地从一个不是main.rs的文件中调用了一个定义在文件中的函数。
super关键字
超级
如果我们的文件组织有多个目录,全称就会变得太长。假设出于某种原因,我们想从print_user_route调用print_health_route。这两个路径分别是crate::routes::health_route和crate::routes::user_route。
我们可以使用全称crate::routes::health_route::print_health_route()来调用它,但我们也可以使用相对路径super::health_route::print_health_route();。注意,我们用super来指代父级范围。
模块路径中的super关键字指的是父级范围
pub fn print_user_route() { crate::routes::health_route::print_health_route(); // can also be called using super::health_route::print_health_route();
println!("user_route"); }
|
use关键字
在上面的例子中,如果使用完全合格的名称,甚至是相对名称,都是很繁琐的。为了缩短名称,我们可以使用use关键字,将路径绑定到一个新的名称或别名上。
use关键字用于缩短模块的路径
pub fn print_user_route() { crate::models::user_model::print_user_model(); println!("user_route"); }
|
上述代码可以重构为:
use crate::models::user_model::print_user_model;
pub fn print_user_route() { print_user_model(); println!("user_route"); }
|
我们可以不使用print_user_model这个名字,而是把它别名为其他名词如
:
use crate::models::user_model::print_user_model as log_user_model;
pub fn print_user_route() { log_user_model(); println!("user_route"); }
|
外部模块
添加到Cargo.toml中的依赖关系对项目中的所有模块都是可用的,我们就不需要明确地导入或声明任何东西来使用一个依赖关系。
外部依赖对项目中的所有模块都是全局可用的。
例如,假设我们将rand crate添加到我们的项目中。我们可以在我们的代码中直接使用它,例如。
pub fn print_health_route() { let random_number: u8 = rand::random(); println!("{}", random_number); println!("health_route"); }
|
我们也可以用使用来缩短路径:
use rand::random;
pub fn print_health_route() { let random_number: u8 = random(); println!("{}", random_number); println!("health_route"); }
|
总结
- 模块系统是明确的--与文件系统没有1:1的映射关系
- 我们将一个文件声明为其父级的模块,而不是其本身。
- mod 关键字用于声明子模块
- 我们需要明确地将函数、结构等声明为公共的,这样它们就可以被其他模块所使用。
- pub关键字使事物公开。
- use关键字用于缩短模块的路径
- 我们不需要明确地声明第三方模块