Skip to content

模块系统与crate组织

1. 这是什么

Rust 的模块系统,解决的是:代码如何被拆分、命名、暴露和复用
crate 组织则是在更大一级回答:一个项目要由哪些库或程序单元组成

一句话理解:

  • module(模块)更像项目内部的分区
  • crate 更像一个独立编译单元 / 包装边界
  • Cargo 则负责把这些边界组织起来

如果说前面所有权、生命周期、trait 都更偏“语言内部规则”,那模块系统和 crate 组织就是 Rust 从“会写语法”走向“能组织工程”的关键一步。

2. 为什么重要

一开始写练习代码时,所有内容都塞进 main.rs 似乎也能跑。
但项目一旦变大,就会立刻遇到这些问题:

  • 文件越来越长
  • 逻辑边界不清楚
  • 哪些函数该公开、哪些不该公开不明确
  • 不同功能之间耦合越来越重

Rust 的模块系统重要就在于,它鼓励你把这些边界在代码层面表达清楚:

  • 哪些内容属于哪个模块
  • 哪些 API 对外可见
  • 哪些实现细节只留在内部
  • 哪些功能适合拆成独立 crate

这会直接影响代码可维护性。

3. 先建立直觉

先用最朴素的方式理解:

  • module:给代码分房间
  • pub:决定哪些门对外打开
  • crate:整栋房子的边界

比如:

rust
mod user;
mod config;

表示当前 crate 里有两个模块。
而:

rust
pub fn run() {}

则表示这个函数可以被模块外使用。

所以 Rust 的模块组织不是“纯目录问题”,而是“代码可见性 + 命名空间 + 文件结构”一起工作的结果。

4. 核心内容

4.1 crate 是什么

crate 可以先粗略理解成 Rust 的一个编译单元。
常见有两类:

  • binary crate:可执行程序
  • library crate:可复用库

例如:

  • src/main.rs 常对应 binary crate 入口
  • src/lib.rs 常对应 library crate 入口

所以学 crate 组织,第一层直觉就是:

  • 我是在写一个程序
  • 还是在写一个可以被别人复用的库

4.2 module 是什么

module 用于把 crate 内部代码继续拆分成更清晰的结构。

例如:

rust
mod auth;
mod db;
mod service;

这表示当前 crate 内部按功能切成了几个模块。

它的核心价值是:

  • 减少一个文件塞满所有逻辑
  • 建立更清晰的命名空间
  • 让“谁该依赖谁”更容易管理

4.3 pub 决定暴露边界

Rust 默认是偏保守的。
很多东西默认对外不可见,只有显式写 pub 才会暴露出去。

例如:

rust
mod math {
    pub fn add(a: i32, b: i32) -> i32 {
        a + b
    }

    fn helper() {}
}

这里:

  • add 可以被模块外使用
  • helper 只能在当前模块内部使用

这套默认私有、按需公开的策略很重要,因为它会逼你思考:

  • 哪些是稳定接口
  • 哪些只是内部实现细节

4.4 路径与 use

Rust 里访问模块内容时,经常会看到路径和 use

rust
use crate::service::UserService;

可以理解为:

  • crate 表示当前 crate 根
  • 后面沿着模块路径找到具体类型或函数

use 的作用更像“把这个名字引入当前作用域,后面写起来更简洁”。

所以它本质是在处理命名空间,而不只是“语法装饰”。

4.5 文件结构与模块结构通常会对应

Rust 常见组织方式是让文件结构和模块结构保持一致。
例如:

text
src/
├── main.rs
├── config.rs
└── service/
    ├── mod.rs
    └── user.rs

或者较新的常见风格:

text
src/
├── main.rs
├── config.rs
└── service/
    └── user.rs

然后在代码里声明模块关系。

重点不是死记某一种目录写法,而是理解:

  • 文件结构最好服务于模块边界
  • 模块边界最好服务于职责划分

4.6 为什么不是一上来就全 pub

很多初学者为了省事,会把看到的一切都设成 pub
短期似乎顺手,但长期会带来问题:

  • 内部实现被过度暴露
  • API 边界变模糊
  • 重构时牵一发而动全身

更好的思路是:

  • 默认先私有
  • 真正需要给外部调用的,再公开

这和很多成熟工程里的封装原则是一致的。

4.7 crate 什么时候该拆

随着项目变大,你可能会问:

  • 这个功能继续放模块里就够了
  • 还是应该拆成独立 crate

一个朴素判断方式是看它是否已经具备这些特征:

  • 逻辑相对独立
  • 对外接口比较清晰
  • 未来可能被多个地方复用
  • 希望减少主程序和它的耦合

如果都成立,拆 crate 往往比在一个大文件树里继续堆模块更合理。

4.8 module / crate 组织其实是在设计依赖方向

更深一层看,模块系统不只是文件摆放问题,而是在设计:

  • 谁可以访问谁
  • 谁依赖谁
  • 哪些地方是上层协调
  • 哪些地方是底层实现

如果组织得好:

  • 依赖方向清晰
  • 代码更容易替换和测试
  • 边界更容易维护

如果组织得乱:

  • 文件虽然分开了
  • 但依赖依然一团乱麻

所以“拆文件”不等于“模块设计做好了”。

5. 常见误区

5.1 误区一:模块系统只是目录结构问题

不是。
目录只是表象,真正重要的是:

  • 命名空间
  • 可见性
  • 依赖边界
  • 职责划分

5.2 误区二:能编译通过就说明组织没问题

不一定。
很多项目能跑,但模块边界已经很糟,只是问题还没大到爆出来。

5.3 误区三:pub 越多越省事

短期也许省事,长期几乎一定增加维护负担。
公开接口一旦多起来,后续重构空间就会被压缩。

5.4 误区四:模块拆得越细越高级

也不是。
拆太细会让跳转路径和心智成本变高。关键不在“细”,而在“边界是否自然、职责是否清楚”。

6. 一个更实用的判断思路

当你准备组织 Rust 项目结构时,可以先问自己:

  1. 这是一个程序 crate 还是库 crate
  2. 哪些逻辑应该归在同一个模块里
  3. 哪些内容是真正要暴露给外部的 API
  4. 这里继续做模块划分就够,还是已经值得拆成独立 crate
  5. 当前的组织方式有没有让依赖方向变得更清晰

只要这几个问题想明白,模块设计通常不会太偏。

7. 学习建议

学习模块系统与 crate 组织时,建议按这个顺序:

  1. 先把单文件程序拆成两个模块
  2. 练习 modpubuse 的基本关系
  3. 再看 main.rslib.rs 的区别
  4. 再练习把一组功能整理成更清晰的目录结构
  5. 最后再接触 workspace 和多 crate 项目

这样你会从“语法会写”逐步过渡到“结构会设计”。

8. 自测标准

  • 能解释 module 和 crate 分别在解决什么问题
  • 能区分 main.rslib.rs 的常见角色
  • 能理解 pub 为什么是在定义边界
  • 能看懂 use crate::... 这种路径引用
  • 能判断什么时候该继续拆模块,什么时候值得拆成独立 crate