rust 模块
一、模块系统的基础
1. 模块的定义
模块是 Rust 中组织代码的基本单元。通过模块,你可以将代码划分为逻辑单元,控制可见性,避免命名冲突。
-
定义模块:
mod my_module { // 模块内容 }
-
模块的作用域:
模块内的项(函数、结构体、枚举等)默认是私有的,只能在模块内部访问。使用pub
关键字可以公开这些项。
2. 模块的可见性
- 私有(默认):只能在模块内部访问。
- 公开(
pub
):可以在模块外部访问。 - 受限公开:
pub(crate)
:在整个 crate 内可见。pub(super)
:在父模块中可见。pub(in path)
:在指定路径下可见。
示例:
mod outer {
pub mod inner {
pub(crate) fn foo() {} // 整个 crate 可见
pub(super) fn bar() {} // 父模块(outer)可见
pub(in crate::outer) fn baz() {} // 仅在 outer 模块中可见
}
}
二、模块与文件系统
Rust 的模块系统与文件系统紧密相关。模块可以定义在单个文件中,也可以拆分到多个文件中。
1. 模块的文件映射
-
规则:
- 当你在一个文件中声明
mod module_name;
时,Rust 会查找:- 同级目录下的
module_name.rs
文件,或 - 同级目录下的
module_name/mod.rs
文件。
- 同级目录下的
- 当你在一个文件中声明
-
示例:
my_crate/ ├── Cargo.toml └── src/ ├── main.rs ├── network.rs // 对应 `mod network;` └── client/ ├── mod.rs // 对应 `mod client;` 中的内容 └── api.rs // 子模块可通过 `mod api;` 引入
2. 文件内容示例
-
src/main.rs:
mod network; // 引入 network.rs 或 network/mod.rs fn main() { network::connect(); }
-
src/network.rs:
pub mod client; // 引入 client.rs 或 client/mod.rs pub fn connect() {}
-
src/client/mod.rs:
pub mod api; // 引入 api.rs pub fn connect() {}
-
src/client/api.rs:
pub fn connect() {}
三、路径与 use
关键字
1. 路径类型
- 绝对路径:从 crate 根开始,以
crate::
或第三方 crate 名开头。crate::network::client::connect();
- 相对路径:从当前模块开始,用
self
(当前模块)、super
(父模块)或模块名。self::client::connect(); super::network::connect();
2. 使用 use
引入路径
use
关键字用于将路径引入当前作用域,避免重复写完整路径。
-
基本用法:
use crate::network::client; fn main() { client::connect(); }
-
重命名:
use std::fmt::Result as FmtResult;
-
合并引入:
use std::{cmp::Ordering, io};
-
通配符引入:
use std::collections::*; // 引入所有项
3. 再导出(Re-export)
使用 pub use
将内部项公开到当前作用域,方便外部调用。
示例:
// 在 network/mod.rs 中
pub use self::client::connect; // 外部可直接通过 network::connect 访问
四、模块的高级用法
1. 测试模块
使用 #[cfg(test)]
标记测试模块,测试代码仅在测试时编译。
示例:
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}
2. 全局可见的 Prelude
在 lib.rs
或 main.rs
中定义一个 prelude
模块,导出常用项。
示例:
pub mod prelude {
pub use crate::network::client::Client;
pub use crate::types::*;
}
3. 处理循环依赖
避免循环依赖,或使用 pub(crate)
限制可见性,或重构代码结构。
五、模块的最佳实践
- 按功能划分模块:将相关的功能放在同一个模块中。
- 避免过深的嵌套:模块嵌套不宜过深,通常不超过 3 层。
- 使用
pub use
简化接口:将内部模块的常用项再导出,方便外部调用。 - 合理使用可见性:避免过度公开,保持模块的封装性。
六、常见问题与解决方案
1. 找不到模块
- 原因:文件路径与
mod
声明不匹配。 - 解决:检查文件路径和
mod
声明是否一致。
2. 私有项无法访问
- 原因:未使用
pub
关键字。 - 解决:检查是否遗漏
pub
。
3. 循环依赖
- 原因:模块之间相互引用。
- 解决:重构代码,或将公共部分提取到新模块。
七、完整示例
目录结构
my_app/
├── Cargo.toml
└── src/
├── main.rs
├── lib.rs
├── utils/
│ ├── mod.rs
│ └── math.rs
└── models/
├── mod.rs
└── user.rs
代码示例
-
src/lib.rs:
pub mod utils; pub mod models;
-
src/utils/mod.rs:
pub mod math;
-
src/utils/math.rs:
pub fn add(a: i32, b: i32) -> i32 { a + b }
-
src/models/mod.rs:
pub mod user;
-
src/models/user.rs:
pub struct User { pub name: String, }
-
src/main.rs:
use my_app::utils::math; use my_app::models::user::User; fn main() { println!("2 + 3 = {}", math::add(2, 3)); let user = User { name: String::from("Alice") }; println!("User name: {}", user.name); }