搞懂 Rust 模块(mod):以 crate 根为起点,告别代码堆成山,写出结构化程序
本文介绍了Rust中模块系统的核心概念和使用方法。模块是组织代码的基础单元,通过crate根(lib.rs或main.rs)作为起点构建层级结构。文章详细讲解了三种定义模块的方式:内部模块直接嵌套、文件模块单独存放、目录模块组织复杂功能。重点说明了如何使用pub控制可见性,以及通过绝对路径、相对路径和use语句引用模块内容。最后提供了常见问题解决和最佳实践建议,强调合理控制模块深度和API公开范围
在 Rust 中,mod
(模块)是组织代码的 “乐高积木”—— 它能帮你把零散的函数、结构体、枚举等 “零件” 按功能分类,拼出结构清晰的程序。如果你写过其他语言,可以把模块理解为 “文件夹”:就像用文件夹整理文件一样,模块帮你整理代码,避免杂乱无章。
而这一切的 “整理逻辑”,都围绕一个核心 ——crate 根。今天我们就从 “crate 根” 说起,一步步搞懂模块的使用。
一、先搞懂:什么是 crate 根?
在 Rust 中,“crate” 可以理解为 “项目”(比如一个可执行程序或一个库)。每个 crate 都有一个 “起点”,这个起点就是 crate 根:
- 如果你写的是一个库(比如供其他项目引用的工具包),crate 根是
src/lib.rs
; - 如果你写的是一个可执行程序(比如命令行工具、应用),crate 根是
src/main.rs
。
简单说,crate 根就是项目的 “主文件”,所有模块最终都像树枝一样,从这个 “主根” 延伸出去,形成一棵 “模块树”。
二、模块的核心作用
为什么需要模块?想象一下,如果你写一个电商系统,里面有用户登录、商品管理、订单处理等功能。如果把所有代码堆在一个文件里,找个函数都要翻半天。而模块能帮你:
- 分类功能:把 “用户相关” 的代码放进
user
模块,“商品相关” 的放进product
模块; - 控制访问:比如
user
模块里的密码加密函数,不想让外部随便调用,就可以设为 “私有”; - 避免重名:
user
模块里可以有get_info
函数,product
模块里也可以有同名函数,互不冲突。
三、定义模块的三种方式(从 crate 根开始)
模块的定义方式和文件结构紧密相关,根据代码多少,有三种常用形式,且都以 crate 根为起点构建层级。
1. 内部模块:简单功能,直接写在 crate 根里
如果功能很简单(比如几个工具函数),可以直接在 crate 根(如 lib.rs
)里用 mod 模块名 { ... }
定义模块。这种模块就像 “主文件里的小文件夹”,直接嵌套在 crate 根下。
示例(src/lib.rs
):
// 这是 crate 根(lib.rs)
// 定义一个名为 math 的内部模块(直接嵌套在 crate 根下)
mod math {
// 模块内的函数(默认“私有”,只能在 math 模块里用)
fn add(a: i32, b: i32) -> i32 {
a + b
}
// 用 pub 修饰的函数是“公开”的,模块外也能调用
pub fn multiply(a: i32, b: i32) -> i32 {
a * b
}
// 模块里还能嵌套子模块(就像文件夹里套子文件夹)
pub mod advanced {
// 这个函数是 advanced 子模块的公开函数
pub fn square(n: i32) -> i32 {
n * n
}
}
}
// 在 crate 根里使用 math 模块的功能
pub fn use_math() {
// 错误:add 是 math 模块的私有函数,外面用不了
// let sum = math::add(2, 3);
// 正确:multiply 是公开函数,能直接用
let product = math::multiply(2, 3);
println!("2 × 3 = {}", product); // 输出:2 × 3 = 6
// 访问子模块 advanced 的公开函数(路径:math::advanced::square)
let square = math::advanced::square(4);
println!("4 的平方 = {}", square); // 输出:4 的平方 = 16
}
2. 文件模块:代码较多?单独放一个 .rs
文件
如果模块里的代码太多(比如超过 100 行),塞在 crate 根里会显得臃肿。这时可以把模块代码拆到单独的 .rs
文件中,再在 crate 根里 “声明” 这个模块。
步骤演示:
假设我们要做一个 “字符串工具” 模块(utils
),代码较多,单独放文件。
(1)创建模块文件:在 src
目录下新建 utils.rs
,写模块内容(不用重复写 mod utils
):
// src/utils.rs(模块内容)
// 公开函数:格式化字符串
pub fn format_str(s: &str) -> String {
format!("【{}】", s) // 给字符串加个括号,比如 "test" → "【test】"
}
// 私有函数:模块内部用的辅助函数(外面看不到)
fn trim_whitespace(s: &str) -> &str {
s.trim()
}
(2)在 crate 根中声明模块:在 src/lib.rs
里用 mod utils;
告诉 Rust:“有个叫 utils 的模块,代码在 utils.rs 里”。这样 utils
就成了 crate 根的直接子模块。
// src/lib.rs(crate 根)
// 声明 utils 模块(关联 src/utils.rs)
mod utils;
// 使用 utils 模块的公开函数
pub fn use_utils() {
let result = utils::format_str("hello");
println!("格式化后:{}", result); // 输出:格式化后:【hello】
}
3. 目录模块:复杂模块?用文件夹组织多个文件
如果一个模块包含多个子功能(比如 “计算模块” 里有 “基础计算” 和 “高级计算”),可以用文件夹来组织。这种方式就像 “用一个大文件夹装多个相关的小文件”。
目录结构示例:
我们要做一个 calc
模块,包含 basic
(基础计算)和 advanced
(高级计算)两个子模块,结构如下:
src/
├── lib.rs # crate 根
└── calc/ # 大模块:calc(文件夹)
├── basic.rs # 子模块:calc::basic(基础计算)
├── advanced.rs # 子模块:calc::advanced(高级计算)
└── mod.rs # calc 模块的“入口文件”(声明子模块)
步骤演示:
(1)写子模块代码:
src/calc/basic.rs
(基础计算,比如加法):
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
src/calc/advanced.rs
(高级计算,比如乘方):
pub fn power(n: i32, exp: u32) -> i32 {
(0..exp).fold(1, |acc, _| acc * n) // 计算 n 的 exp 次方
}
(2)写模块入口文件(mod.rs):
在 calc
文件夹里新建 mod.rs
,声明子模块(告诉 Rust 这个文件夹里有哪些子模块):
// src/calc/mod.rs
// 声明子模块(用 pub 修饰,允许外部访问)
pub mod basic;
pub mod advanced;
// calc 模块自身的函数(比如版本信息)
pub fn version() -> &'static str {
"v1.0.0"
}
(3)在 crate 根中声明 calc 模块:
在 src/lib.rs
里用 mod calc;
关联 calc
文件夹,使其成为 crate 根的子模块:
// src/lib.rs(crate 根)
// 声明 calc 模块(关联 calc/ 文件夹)
mod calc;
// 使用 calc 模块及其子模块
pub fn use_calc() {
// 调用 calc::basic::add(路径:crate 根 → calc → basic → add)
let sum = calc::basic::add(2, 3);
println!("2 + 3 = {}", sum); // 输出:2 + 3 = 5
// 调用 calc::advanced::power
let pow = calc::advanced::power(2, 3); // 2 的 3 次方
println!("2^3 = {}", pow); // 输出:2^3 = 8
// 调用 calc 模块自身的函数
println!("calc 模块版本:{}", calc::version()); // 输出:calc 模块版本:v1.0.0
}
小技巧:Rust 2018 之后,可以省略 mod.rs
。直接在 crate 根里嵌套声明子模块即可:
// src/lib.rs
// 直接声明 calc 模块,并嵌套子模块(自动关联 calc/basic.rs 和 calc/advanced.rs)
pub mod calc {
pub mod basic;
pub mod advanced;
pub fn version() -> &'static str { "v1.0.0" }
}
四、控制可见性:谁能访问模块里的内容?
模块里的内容(函数、结构体等)默认是 “私有” 的(只有自己能访问)。如果想让外部访问,需要用 pub
关键字 “公开”。就像文件夹的权限:有的文件能被所有人看,有的只能自己看。
常见的可见性修饰符:
修饰符 | 含义 | 例子场景 |
---|---|---|
无(默认) | 仅当前模块内可访问 | 模块内部的辅助函数 |
pub |
所有模块都能访问(只要路径正确) | 对外提供的 API 函数 |
pub(crate) |
仅当前 crate 内可访问(对外不可见) | 项目内部共用的工具函数 |
pub(super) |
仅父模块可访问 | 父模块需要调用的子模块内部函数 |
示例:
// 公开模块(外面能看到这个模块)
pub mod public_mod {
// 公开函数(所有模块都能调用)
pub fn public_func() {
println!("我是公开函数");
}
// 私有函数(只有 public_mod 内部能调用)
fn private_func() {
println!("我是私有函数");
}
// 仅当前 crate 内可访问的函数
pub(crate) fn crate_only_func() {
println!("只有本项目能调用我");
}
// 子模块
pub mod inner_mod {
// 仅父模块(public_mod)可访问
pub(super) fn parent_only_func() {
println!("只有父模块能调用我");
}
}
}
// 在其他地方使用
pub fn test_visibility() {
public_mod::public_func(); // 可以调用(pub)
public_mod::crate_only_func(); // 可以调用(pub(crate),因为在同一个项目)
// 错误:private_func 是私有函数
// public_mod::private_func();
// 错误:parent_only_func 仅允许父模块(public_mod)调用
// public_mod::inner_mod::parent_only_func();
}
五、引用模块:怎么找到要调用的函数?
模块里的函数就像 “文件夹里的文件”,要调用它,得告诉 Rust 它在 “哪个文件夹里”(路径)。有两种常用路径:
1. 绝对路径:从 crate 根出发(推荐)
绝对路径以 crate::
开头,就像从 “项目根目录” 出发找文件(比如 crate::calc::basic::add
)。优点是路径固定,不管在哪个模块里调用,都能准确找到。
示例:
// src/lib.rs(crate 根)
mod calc {
pub mod basic {
pub fn add(a: i32, b: i32) -> i32 { a + b }
}
}
// 另一个模块(other_mod)
mod other_mod {
pub fn use_add() {
// 绝对路径:从 crate 根出发 → calc → basic → add
let sum = crate::calc::basic::add(2, 3);
println!("sum: {}", sum);
}
}
2. 相对路径:从当前模块出发
相对路径以 self::
(当前模块)或 super::
(父模块)开头,就像 “从当前文件夹出发找文件”。适合在模块内部或相邻模块间调用。
示例:
mod parent {
pub mod child {
pub fn func() {
println!("我是 child 模块的函数");
}
}
pub fn call_child() {
// 相对路径:从当前模块(parent)出发 → child → func
child::func();
}
}
pub fn call_parent() {
// 相对路径:从当前模块(crate 根)出发 → parent → call_child
parent::call_child();
}
3. 用 use
简化路径:少写重复代码
如果一个函数要调用很多次,每次都写长路径很麻烦。可以用 use
关键字把路径 “导入” 当前模块,之后直接用函数名调用。
示例:
// 导入绝对路径:crate::calc::basic::add
use crate::calc::basic::add;
pub fn demo() {
// 直接用 add,不用写完整路径
let sum1 = add(2, 3);
let sum2 = add(4, 5);
println!("sum1: {}, sum2: {}", sum1, sum2);
}
// 也可以导入整个模块
use crate::calc::advanced;
pub fn demo2() {
// 用“模块名::函数名”调用
let pow = advanced::power(2, 3);
println!("2^3: {}", pow);
}
六、常见问题与最佳实践
-
“模块找不到” 怎么办?
检查:① 文件名 / 目录名是否和模块名一致(比如mod utils;
对应utils.rs
);② 是否在父模块中声明了子模块(比如calc
模块是否在lib.rs
中用mod calc;
声明);③ 路径是否正确(绝对路径用crate::
开头,相对路径是否漏了super::
)。 -
模块层级不要太深
建议模块层级不超过 3-4 层(比如a::b::c::d
就有点深了),否则路径太长,不好维护。 -
crate 根只做 “入口”
lib.rs
或main.rs
里尽量少写具体逻辑,只用来声明模块(比如mod user; mod product;
),让代码结构更清晰。 -
公开 API 要 “精挑细选”
不是所有函数都要pub
,只公开必须让外部使用的功能,其他逻辑用私有修饰符隐藏,减少误用风险。
总结
模块是 Rust 代码组织的核心,记住三个关键点:
- crate 根(
lib.rs
或main.rs
)是所有模块的起点,就像项目的 “主目录”; - 用
mod
定义模块,可嵌套在文件内、单独放.rs
文件或用文件夹组织; - 用
pub
控制可见性,用绝对路径(crate::xxx
)或相对路径引用模块内容,配合use
简化调用。
掌握模块系统,你就能写出结构清晰、易于维护的 Rust 代码了!
更多推荐
所有评论(0)