Rust 的模块(Module)和包(Crate)系统
目录
📝 文章摘要
Rust 的模块(Module)和包(Crate)系统是其强大封装性和可维护性的来源,但它也是新手(甚至是有经验的开发者)最容易混淆的概念之一。本文将深入剖析 Rust 的模块系统,厘清 Crate(包)、Module(模块)、mod 关键字(内联与文件)、use(导入)、super(父级)和 self(当前)的精确含义,以及 Rust 2018 引入的“路径清晰化”(Path Clarity)是如何简化模块管理的。
一、背景介绍
C/C++ 使用 #include(头文件)来组织代码,这本质上是“文本复制”,容易导致命名空间污染和极长的编译时间。Java/Python 使用“文件即模块”的系统,相对清晰,但缺乏对“私有性”的精细控制。
Rust 的模块系统是一个显式(Explicit)的、树状(Hierarchical)的系统。它强制你明确声明:
- Crate(:Rust 的编译单元(
lib或bin)。 - Module(模块):Crate 内部的命名空间(Namespace)和私有性边界(Privacy Boundary)。
- 路径(Path):如何从 Crate 根(`crate::)找到一个 Item。
理解“模块树”(Module Tree)是理解 Rust 编译的第一步。
二、原理详解
2.1 Crate:编译单元
一个 Crate 是最小的编译单元。
- Binary Crate (二进制包):`src/main.s` 是它的 Crate 根(Crate Root)。
- Library Crate (库包):`src/librs` 是它的 Crate 根。
Crate 根(main.rs 或 lib.rs)是编译器查找模块树的起点。
2.2 mod:定义模块
mod 关键字定义一个模块。它有两种形式:
1. 内联(Inline)模块:
// src/lib.rs (Crate Root)
// 定义 `front_of_house` 模块
mod front_of_house {
// 定义 `hosting` 子模块
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
2. 文件(File)模块 (Rust 20+):
这是 Rust 2018 之后推荐的方式,更简洁。
文件结构:
src/
├── lib.rs
└── front_of_house/
├── hosting.rs
└── mod.rs
src/lib.rs (Crate Root)
// 告诉 Rust 在 "src/front_of_house.rs"
// 或 "src/front_of_house/mod.rs" 中查找内容
pub mod front_of_house;
src/front\_of\_housers (模块根)
// 告诉 Rust 在 "src/front_of_house/hosting.rs"
// 中查找 `hosting` 模块的内容
pub mod hosting;
src/front\_of\_house/ng.rs
pub fn add_to_waitlist() {}
2.3 pub:可见性(Visibility)
Rust 中的所有 Item(函数、结构体、模块等)默认是私有(Private) 的。
- 私有(n`):只能被当前模块及其子模块访问。
- 公共(
pub fn):可以被外部模块访问。
pub(crate) 和 pub(super)pub 还可以是受限的:
- `pub(crate):只在当前 Crate 内可见(对库的内部实现很有用)。
pub(super):只在父模块(super)可见。
2.4 use:导入路径
mod 定义了模块树,use 则创建了“快捷方式”(Shortcuts)。
use 总是从 Crate 根(或 self / super)开始。
// src/lib.rs
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
// 绝对路径 (Absolute Path)
// `crate` 关键字代表当前 Crate 的根
use crate::front_of_house::hosting;
// 相对路径 (Relative Path)
// mod back_of_house {
// use super::front_of_house::hosting;
// }
pub fn eat_at_restaurant() {
// `use` 之后,我们可以使用 `hosting` 快捷方式
hosting::add_to_waitlist();
}
2.5 super 和 self
self:指向当前模块。super:指向父模块。
mod outer {
fn private_fn() {}
mod inner {
fn inner_fn() {
// 相对路径:调用同级
self::other_inner_fn();
// 相对路径:调用父级
super::private_fn();
}
fn other_inner_fn() {}
}
}
三、代码实战
3.1 实战:构建一个 my_lib 库
我们将构建一个库,其文件结构如下,并演示如何正确地组织 mod 和 use。
文件结构:
my_lib/
├── Cargo.toml
└── src/
├── lib.rs (Crate Root)
├── network/
│ ├── client.rs (mod client)
│ └── mod.rs (mod network)
└── utils.rs (mod utils)
src/lib.rs (Crate Root)
// 1. 声明 `network` 模块。
// Rust 会查找 "src/network.rs" 或 "src/network/mod.rs"
pub mod network;
// 2. 声明 `utils` 模块。
// Rust 会查找 "src/utils.rs"
pub mod utils;
// 3. 将 `network::client` 中的 `connect` 函数
// "提升" (re-export) 到库的顶层。
// 外部用户现在可以调用 `my_lib::connect()`
pub use crate::network::client::connect;
pub fn main_api_call() {
// 使用绝对路径
crate::network::client::connect();
// 使用相对路径 (因为 utils 在 lib.rs 中声明)
utils::helper();
}
srctwork/mod.rs (network 模块)
// 声明 `client` 子模块。
// Rust 会查找 "src/network/client.rs"
pub mod client;
// (我们也可以在这里定义 network 相关的函数)
fn internal_network_fn() {
// ...
}
src/network/client.rs (client 模块)
pub fn connect() {
println!("Connecting to network...");
// 访问同级模块 (network::internal_network_fn)
// 必须使用 `super` (父级)
super::internal_network_fn();
// 访问 Crate 根的 `utils` 模块
// 必须使用 `crate`
crate::utils::helper();
}
src/utils.rs (utils 模块)
pub fn helper() {
println!("Utils helper called.");
}
3.2 实战:`pub struct 的可见性
pub 的粒度是细致的。
mod my_mod {
#[derive(Debug)]
pub struct MyStruct {
pub public_field: i32,
private_field: i32, // 默认私有
}
impl MyStruct {
pub fn new() -> Self {
Self { public_field: 1, private_field: 10 }
}
}
}
fn main() {
let mut s = my_mod::MyStruct::new();
// ✓ OK: 字段是 pub 的
s.public_field = 100;
// ❌ 编译错误:field `private_field` is private
// s.private_field = 200;
// ❌ 编译错误:`private_field` 是私有的,
// 我们无法在模块外直接构造
// let s2 = my_mod::MyStruct {
// public_field: 1,
// private_field: 2,
// };
}
pub struct 并不意味着其所有字段都是 pub。这种封装性(Encapsulation)强制用户使用 MyStruct::new()(构造函数)来创建实例,确保了数据的不变性(Invariants)。
四、结果分析
4.1 模块树(Module Tree)可视化
我们实战中的 my_lib Crate,其“模块树”在编译时是这样的:

Rust 2018 vs 2015
在 Rust 2015(旧版)中,路径解析是混乱的。use foo 既可能指 Crate 根的 foo,也可能指当前模块的 foo。
Rust 2018 路径清晰化(Path Clarity)规定:
use some::path:总是从 C Crate 根(crate::)开始(除非是self::或super::)。- 解决了所有歧义。-----
五、总结与讨论
5.1 核心要点
- Crate 根:
src/ain.rs或src/lib.rs,是模块树的起点。 mod foo;:声明一个模块,告诉编译器在src/foo.rs或src/foo/mod.rs中查找。mod foo... }:内联定义一个模块。use:创建路径的“快捷方式”,不影响模块树或私有性。- 默认私有:Rust 的一切默认都是私有的。
pub用于暴露 API。 - 路径:
crate::(绝对路径)、super::(父级)、self::(当前)是访问模块树的锚点。
5.2 讨论问题
- 为什么 Rust 2018 倾向于使用
src/oo.rs而不是src/foo/mod.rs?(提示:mod.rs在 IDE 标签页中难以区分) use 的“提升”(Re-exporting)——pub use crate::internal::MyType;`——在设计库 API 时有何重要意义?- “Crate” 和 “Package” 在
Cargo.toml的上下文中有什么区别?(提示:一个 Package 可以包含多个 Crate)
参考链接
更多推荐
所有评论(0)