跳到主要内容

Rust 模块系统的清晰解释

提示

本文为转载内容,由Google翻译生成,原文https://www.sheshbabu.com/posts/rust-module-system/

Rust 的模块系统令人惊讶地令人困惑并给初学者带来了很多挫败感。

在这篇文章中,我将使用实际示例解释模块系统,以便您清楚地了解其工作原理并可以立即开始在您的项目中应用它。

由于 Rust 的模块系统非常独特,我请求读者以开放的心态阅读这篇文章,不要将其与其他语言中模块的工作方式进行比较。

让我们使用这个文件结构来模拟一个真实世界的项目:

my_project
├── Cargo.toml
└─┬ src
├── main.rs
├── config.rs
├─┬ routes
│ ├── health_route.rs
│ └── user_route.rs
└─┬ models
└── user_model.rs

这些是我们使用模块的不同方式: rust-module-system-1 这 3 个例子足以解释 Rust 的模块系统如何工作。

示例1

让我们从第一个例子开始——在main.rs中导入config.rs

// main.rs
fn main() {
println!("main");
}
// config.rs
fn print_config() {
println!("config");
}

每个人犯的第一个错误就是仅仅因为我们有诸如等文件config.rshealth_route.rs我们就认为这些文件是modules,并且可以从其他文件中导入它们。

以下是我们看到的内容(文件系统树)和编译器看到的内容(模块树): rust-module-system-2

令人惊讶的是,编译器只看到crate即我们的main.rs文件模块。这是因为我们需要在 Rust 中显式构建模块树 - 文件系统树到模块树之间没有隐式映射。

我们需要在 Rust 中明确构建模块树,没有隐式映射到文件系统

要将文件添加到模块树中,我们需要使用关键字将该文件声明为子模块mod。接下来让人感到困惑的是,你会认为我们在同一个文件中将文件声明为模块。但我们需要在另一个文件中声明它!因为我们只main.rs在模块树中,所以我们config.rs在中将其声明为子模块main.rs

mod 关键字声明一个子模块 该mod关键字的语法如下:

mod my_module;

在这里,编译器在同一目录中寻找my_module.rsmy_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.rsconfig.rs位于同一目录中,因此我们声明配置模块如下:

// main.rs
+ mod config;

fn main() {
+ config::print_config();
println!("main");
}
// config.rs
fn print_config() {
println!("config");
}

我们正在print_config使用::语法访问该函数。

模块树如下所示: rust-module-system-3

我们成功声明了config模块!但这还不足以调用print_config模块内部的函数config.rs。 Rust中的几乎所有内容默认都是私有的,我们需要使用pub关键字将函数公开:

pub 关键字使内容公开

// main.rs
mod config;

fn main() {
config::print_config();
println!("main");
}
// config.rs
- fn print_config() {
+ pub fn print_config() {
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");
}

正如我们前面讨论的,我们只能在同一个目录中使用mod关键字。my_module.rs my_module/mod.rs

因此,为了调用routes/health_route.rs来自内部的函数main.rs,我们需要做以下事情:

  1. 创建一个名为的文件routes/mod.rsroutes在其中声明子模块main.rs
  2. 声明health_route子模块routes/mod.rs并将其公开
  3. 将内部函数health_route.rs公开
my_project
├── Cargo.toml
└─┬ src
├── main.rs
├── config.rs
├─┬ routes
+ │ ├── mod.rs
│ ├── health_route.rs
│ └── 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/mod.rs
+ pub mod health_route;
// routes/health_route.rs
- fn print_health_route() {
+ pub fn print_health_route() {
println!("health_route");
}

模块树如下所示: rust-module-system-4

我们现在可以调用文件夹内的文件中定义的函数。

示例 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");
}

我们想从 mainprint_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");
}

模块树如下所示: rust-module-system-5

等等,我们实际上还没有print_user_model从调用print_user_route!到目前为止,我们只是从 调用了其他模块中定义的函数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_health_route从调用print_user_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缩短路径:

use rand::random;

pub fn print_health_route() {
let random_number: u8 = random();
println!("{}", random_number);
println!("health_route");
}

概括

  • 模块系统是明确的 - 与文件系统没有 1:1 的映射
  • 我们将文件声明为其父级中的模块,而不是在其本身中声明为模块
  • 关键字mod用于声明子模块
  • 我们需要明确将函数、结构等声明为公共,以便它们可以被其他模块使用
  • 关键字pub使事物公开
  • 该use关键字用于缩短模块路径
  • 我们不需要明确声明第三方模块