1. 模块简介

什么是模块?

在 Rust 中,模块是一种组织代码的方式。通过模块,你可以将相关的函数、结构体、枚举、常量等分组在一起,并控制它们的可见性(即哪些部分是公共的,哪些是私有的)。这有助于保持代码清晰、可维护,并避免命名冲突。

Rust 模块系统的优势

  • 封装性:允许你隐藏实现细节,只暴露必要的接口。
  • 代码组织:帮助管理大型代码库,使得代码更容易理解和维护。
  • 命名空间管理:防止名称冲突,尤其是在大型项目中。

2. 声明模块

使用 mod 关键字

要声明一个模块,你需要使用 mod 关键字。例如,要在 main.rs 中定义一个名为 greetings 的模块:

rust 复制代码
// src/main.rs
mod greetings;

fn main() {
    println!("Hello from the main function!");
}

然后,在同一目录下创建一个名为 greetings.rs 的文件来定义该模块的内容:

rust 复制代码
// src/greetings.rs
pub fn hello() {
    println!("Hello from the greetings module!");
}

文件与目录映射到模块

除了上述简单的模块声明外,Rust 还支持更复杂的层次结构。例如,如果你想定义一个子模块,可以在 src 目录下创建一个同名文件夹并在其中添加 mod.rs 文件或直接根据文件路径声明子模块。

例如,创建一个 network 模块和它的子模块 http

复制代码
src
├── main.rs
└── network
    |__ mod.rs
    └── http.rs

然后在 main.rs 中声明:

rust 复制代码
// src/main.rs
mod network;

fn main() {
    network::http::get();
}

而在 src/network/http.rssrc/network/mod.rs 中定义:

rust 复制代码
// src/network/http.rs
pub fn get() {
    println!("Performing an HTTP GET request.");
}
// src/network/mod.rs
pub  mod http;

3. 访问控制

pub 关键字的作用

默认情况下,模块中的所有项都是私有的。这意味着它们只能在定义它们的模块内部访问。若要使某项可以从外部访问,需要使用 pub 关键字将其标记为公共。

例如,在 greetings.rs 中:

rust 复制代码
// src/greetings.rs
pub fn hello() {
    println!("Hello from the greetings module!");
}

fn goodbye() {
    println!("Goodbye from the greetings module!");
}

这里,hello 函数是公共的,而 goodbye 是私有的。因此,你只能在 greetings 模块外部调用 hello 函数。

模块私有性的重要性

模块的私有性有助于封装和隐藏实现细节,仅暴露必要的接口给外部使用者。这是软件设计中的一个重要原则,它增强了代码的安全性和可维护性。

4. 嵌套模块

如何在模块中定义子模块

除了通过文件系统来组织模块之外,你还可以在模块内部直接定义子模块。例如,在 greetings.rs 中定义一个子模块 farewells

rust 复制代码
// src/greetings.rs
pub mod farewells;

fn hello() {
    println!("Hello from the greetings module!");
}

然后在 greetings/farewells.rs 中定义内容:

复制代码
src
├── main.rs
└── greetings
    ├── mod.rs // 或者 greetings.rs
    └── farewells.rs
rust 复制代码
// src/greetings/farewells.rs
pub fn goodbye() {
    println!("Goodbye from the farewells module!");
}

使用路径访问嵌套模块中的项

现在,你可以在 main.rs 中这样访问 farewells 模块中的 goodbye 函数:

rust 复制代码
// src/main.rs
mod greetings;

fn main() {
    greetings::farewells::goodbye();
}

5.实战案例

假设有如下的项目目录结构:

html 复制代码
📦src
 ┣ 📂mod1
 ┃ ┗ 📜test1.rs
 ┗ 📜main.rs

其中,test1.rs 的内容为:

rust 复制代码
// test1.rs
pub fn run() {
  println!("Hello, I'am from mod1::test1!");
}

如果我们需要在main.rs 中访问 test1.rs 中的 run()方法,该如何操作?

我们知道,rust中,mod 关键字用于声明模块,可以放在 main.rs 文件中,也可以放在专门的文件中(mod.rs)。

只声明mod1,可以找到子模块吗?

rust 复制代码
/* 
unresolved module, can't find module file: mod1.rs, or mod1/mod.rs rust-analyzerE0583
file not found for module `mod1`
to create the module `mod1`, create file "src\mod1.rs" or "src\mod1\mod.rs"
if there is a `mod mod1` elsewhere in the crate already, import it with `use crate::...` insteadrustc 
*/
// error[E0583]: file not found for module `mod1` 
mod mod1;
fn main() {
    println!("Hello, world!");
    mod1::test1::run();
}

从编译器的提示可知,当我们在src下的main.rs 中声明一个mod1时, 默认是从同目录的mod1.rs, or mod1/mod.rs 文件开始进行模块查找的。

显然找不到。

解决方案1

mod1 文件夹下创建mod.rs, 在其中声明子模块。

rust 复制代码
// mod1/mod.rs
// 注意 `pub` 关键字
pub mod test1;

解决方案2

使用模块嵌套声明。对于简单的测试,如果我们不想单独创建一个 mod.rs 文件,我们也可以在main.rs 中使用模块嵌套声明,如下:

rust 复制代码
mod mod1 {
  //  注意 `pub` 关键字
  pub mod test1;
}
fn main() {
    println!("Hello, world!");
    // 注意,不能直接通过 test1::run() 访问,必须使用全路径
    mod1::test1::run();
}

6. 总结

1)模块声明与定义的区别:

在 Rust 中,mod 关键字用于声明一个模块。当你在某个文件中使用 mod some_module; 时,Rust 会尝试在同目录下查找名为 some_module.rs 的文件或 some_module/mod.rs(对于子目录模块)作为该模块的内容。

2)文件系统映射模块结构

Rust 强制要求模块的组织必须遵循一定的文件和目录结构。例如,如果你想有一个 mod1::test1 模块结构,你需要在 src 目录下有 mod1/test1.rs 或者 mod1/mod.rsmod1/test1.rs 文件。这是因为 Rust 将这些文件视为模块声明的物理表示形式。

3) 可以在源文件内部直接定义模块的内容

pub mod {} 语法允许你在源文件内部直接定义模块的内容,而不是通过单独的文件或目录结构来组织模块。这对于原型开发测试或当你有一个很小的模块,并且不值得为它创建一个新的文件时非常有用。

如:

rust 复制代码
// src/main.rs

fn main() {
    let resut = utils::add(1, 2);
    println!("resut: {}", resut);
}

// 定义一个名为 utils 的公共模块
mod utils {
  // 在此定义 utils 模块的内容
  pub fn add(a: i32, b: i32) -> i32 {
      a + b
  }
}