Alex
彻底搞懂 Rust 模块系统:它不是文件系统,而是一棵树
很多刚接触 Rust 的开发者,都会在模块系统这里卡一下。
你明明在 src/ 下新建了一个 cpu.rs,结果在 main.rs 里却怎么都引用不到。
如果你有 Java、C++ 或 Go 背景,这种困惑尤其常见,因为我们很容易下意识地认为:
“文件放进目录里了,编译器应该自己知道吧?”
但 Rust 不是这么工作的。
Rust 的模块系统,本质上不是文件系统映射,而是一棵由你手动声明出来的模块树(module tree)。文件只是模块内容的一种承载方式,不是模块关系本身。
理解这一点,Rust 模块系统就会一下子清晰很多。
一句话先讲透:Rust 不会自动扫描你的目录
在 Rust 中,一个项目首先是一个 crate。而一个 crate 的内部结构,不是“哪个文件夹里有什么文件”,而是:
从 crate root 开始,逐层显式声明出来的一棵模块树。
通常:
src/main.rs是二进制 crate 的根(crate root)src/lib.rs是库 crate 的根(crate root)
从这个根开始,Rust 只认你明确声明过的模块。
也就是说:
- 你创建了
cpu.rs,不等于 Rust 自动拥有了cpu模块 - 你必须先在父模块中写
mod cpu; - 这样 Rust 才会把这个文件挂到模块树上
所以 Rust 模块系统最重要的心法是:
先挂载,后使用。
mod 和 use 到底分别干什么?
很多人会把这两个关键字混在一起,其实它们职责完全不同。
mod:声明模块,把树枝接上
mod 的作用,是定义模块结构。
例如:
mod constants;这句话的含义不是“导入 constants”,而是:
告诉编译器:当前模块下面有一个叫 constants 的子模块,请去对应文件里找它。
它相当于在模块树上接了一根树枝。
另一种写法是内联模块:
mod cpu {
fn run() {}
}这表示直接在当前文件里定义一个子模块,而不是去外部文件查找。
use:引用模块内容,创建快捷方式
use 并不会创建模块,它只是为了让路径写起来更方便。
例如:
use crate::constants::MEMORY_SIZE;这句话的前提是:constants 模块必须已经通过 mod constants; 被挂到树上了。
可以记住这句很好用的类比:
mod是接电线(没它没电),use是接插座(为了好用)。
Rust 2018 之后,推荐怎样组织模块?
早期 Rust 很多项目喜欢用 mod.rs,但从 Rust 2018 Edition 开始,更主流、更清晰的组织方式是:
同名文件 + 同名目录
例如:
src/
├── main.rs
├── hardware.rs
└── hardware/
└── cpu.rs这时模块关系通常是这样的:
在 main.rs 中:
mod hardware;在 hardware.rs 中:
pub mod cpu;这样就形成了模块树:
crate
└── hardware
└── cpu这里有个关键点:
hardware.rs是main.rs的子模块- 同时它又是
hardware/cpu.rs的父模块
也就是说,文件系统只是帮助你组织代码;真正决定模块关系的,永远是 mod 声明本身。
跨模块访问,本质上是在树上找路径
当你在 cpu.rs 里想访问别处的内容时,本质上是在模块树里“导航”。
Rust 常见有三种导航方式。
绝对路径:crate::...(最稳)
从 crate 根节点开始找:
use crate::constants::MEMORY_SIZE;这是最推荐新手优先使用的方式,因为它最稳健:
- 不依赖当前文件所在位置
- 重构时更不容易出错
- 路径语义更清晰
相对路径:super::...(去上一层)
super 表示当前模块的父模块:
use super::constants;它适合表达“我在当前模块附近找兄弟节点”,但层级复杂时可读性不如 crate::。
当前模块路径:self::...(在自己房间找)
self 表示当前模块自己:
use self::helper::parse;它常用于模块内部组织代码,让路径显式一些。
真正常见的坑:不是路径,而是可见性(pub)
很多“找不到”的问题,其实并不是模块没声明,而是权限没打开。
Rust 模块系统默认遵循一个原则:
默认私有(private by default)
这和很多语言的默认习惯不同,所以容易踩坑。
模块默认是私有的
mod cpu;表示 cpu 是当前模块的私有子模块,外部不能直接访问。
如果希望别的模块也能访问它,需要:
pub mod cpu;模块里的成员也默认私有
即便模块本身公开了,里面的函数、结构体、常量如果没写 pub,外面一样不能用:
pub mod cpu {
pub fn run() {}
}pub(crate):对整个当前 crate 可见
这是 Rust 里非常实用的可见性控制:
pub(crate) mod cpu;含义是:
- crate 内部随便用
- crate 外部不可见(不会成为公共 API)
“套娃效应”:父模块不开门,子模块 pub 也没用
可见性是逐层生效的。路径上的每一层都必须“打通”。
就像你把卧室门开着,但如果家门锁了,外人仍然进不来。
新手最实用的排错 Checklist
如果你遇到这些错误:
- 找不到模块 / unresolved import
item is private/module is private
按这个顺序排查,通常能很快定位问题:
- 挂载检查:在
main.rs/lib.rs或父模块里写mod xxx;/pub mod xxx;了吗? - 父模块可见性:要走的路径上,每一层父模块都对你可见吗?(父层私有会导致子层
pub也无效) - 成员可见性:
struct/fn/const等具体成员加pub了吗? - 路径检查:新手建议优先用
crate::绝对路径,少绕弯子、也更抗重构。
结语:接受“手动建树”,模块系统就顺了
Rust 的模块系统起初可能觉得繁琐,但一旦你接受了“手动建树”的设定,你会发现它带来的重构安全性和访问控制是极其强大的。
最后用一句话把它钉死:
Rust 的模块系统不是“文件自动组成目录结构”,而是“你通过
mod显式搭建出来的一棵树”。