组织代码:模块、crate 与 cargo 生态
前面二十章讲的是「怎么写对一段代码」。从这一卷起,讲「怎么把一堆代码组织成能交付的工程」。第一件事是分而治之:mod 划分命名空间、pub 控制谁能看见谁、use 把长路径缩短。规模再大,用 crate 和 workspace 组织。而 crates.io 让你站在整个社区的肩膀上。
先厘清四个层级的词
Rust 的代码组织有四个词,从小到大,先分清就不乱:
- 模块(module):一个命名空间,用来分组和控制可见性。用
mod声明,可以嵌套。它是组织代码的最小单位。 - crate:一个编译单元。要么是二进制 crate(有
main,能跑),要么是库 crate(给别人用的代码,无 main)。一个 crate 内部可以有很多模块。 - 包(package):
cargo new建出来的那个东西,由一个Cargo.toml管理,包含一个或多个 crate(通常一个库 + 若干二进制)。 - 工作空间(workspace):把多个相关的 package 组织在一起、共享依赖和构建,适合大项目 / monorepo。
日常小项目,你就一个 package、一个 crate、几个模块,简单得很。词虽多,绝大多数时候你只跟「模块」打交道。
模块:mod、pub、use
模块用 mod 声明,划出一块命名空间。里面的东西默认私有,想让外面用,得显式 pub:
mod network {
pub fn connect() { /* 对外可见 */ }
fn handshake() { /* 私有,只有 network 内部能用 */ }
pub mod http { // 嵌套模块
pub fn get(url: &str) { /* ... */ }
}
}
fn main() {
network::connect(); // 用 :: 走路径
network::http::get("...");
}
路径长了,用 use 把它引进当前作用域,之后直接用短名:
use network::http;
// 之后可以直接写 http::get("..."); 不用每次写全路径
use std::collections::HashMap; // 引入标准库的类型
let mut m = HashMap::new();
真实项目里,模块通常拆成文件:mod network;(不带花括号)告诉编译器「去找 network.rs 或 network/mod.rs」。于是你的目录结构,天然就是模块树。src/main.rs 或 src/lib.rs 是 crate 的根。
可见性默认私有:又一处「安全默认」
注意上面那个细节:Rust 里一切默认私有,你得主动 pub 才对外暴露。这和很多语言(默认 public 或 package-private)相反,而它和这本书反复出现的主题一脉相承 —— 把「安全 / 保守」设成默认,把「开放 / 危险」设成需要显式动作。
这带来一个很实在的好处:一个模块 / 库的公开接口(API)是被精心圈定的一小块,其余全是能随便重构的内部实现。你重构私有部分时,编译器保证不会意外影响到外部调用者 —— 因为他们根本看不见那些私有的东西。这让「维护一个库而不破坏别人」变得可控。pub(crate)、pub(super) 还能做更细的可见性控制(「只在本 crate 内公开」等)。
crates.io:站在社区的肩膀上
第 3 章提过 crates.io 是 Rust 的中央包仓库。这里值得强调它对「落地」的意义:你几乎不用重复造轮子。加依赖只要一行 cargo add,cargo 自动处理版本解析、下载、编译、锁定(Cargo.lock 保证团队和 CI 用完全一致的依赖版本)。生态里那些几乎是事实标准的库,值得记住:
| 要干的事 | 常用 crate |
|---|---|
| 序列化 / 反序列化(JSON 等) | serde + serde_json |
| 异步运行时 | tokio |
| HTTP 客户端 / Web 框架 | reqwest / axum |
| 错误处理 | anyhow(应用)/ thiserror(库) |
| 命令行参数解析 | clap |
| 日志 / 追踪 | tracing |
| 随机数、正则、时间 | rand / regex / chrono |
这些库的质量普遍很高 —— 部分原因正是这本书讲的一切:类型安全和所有权让库作者能设计出「误用即编译错误」的 API,你用起来踏实。
刚起步别过度设计模块结构。一个文件写到几百行了、或者某块逻辑明显自成一体了,再拆成模块 / 文件。让结构跟着代码长,而不是一开始就搭一堆空目录。cargo 和模块系统足够灵活,重构模块结构是很轻的操作(编译器会揪出所有路径错误)。等项目真大到需要拆库了,再上 workspace。
回流
Java / Kotlin:Rust 的 mod ≈ package(命名空间 + 可见性),use ≈ import,crate ≈ 一个 jar / 模块,workspace ≈ Gradle 的多模块项目,crates.io ≈ Maven Central。最大的差别是可见性默认值:Java 默认 package-private,Rust 默认完全私有、且模块可以任意嵌套并精细控制(pub(crate) 等),比 Java 的 public/protected/private/package 四档更灵活。另一个差别是 Rust 的模块树不强制对应目录结构(虽然通常对应),比 Java「包名必须等于目录路径」宽松。
JS / TS:Rust 的 use ≈ import,crate ≈ 一个 npm 包,cargo ≈ npm,crates.io ≈ npm registry,Cargo.lock ≈ package-lock.json。区别是 Rust 的模块可见性是语言强制的(pub),而 JS 靠 export 约定 + 打包工具,且 Rust 没有 JS 那种「一个包能有几十个传递依赖、还常打架」的依赖地狱 —— cargo 的版本解析和编译期检查让依赖管理稳当得多。
这一章的一句话
mod 分命名空间、pub 控可见性(默认私有,又一处安全默认)、use 缩短路径;crate 是编译单元、workspace 组织大项目;crates.io 让你站在社区肩上,cargo 一行命令搞定依赖。让结构跟着代码长,别过度设计。
代码组织好了,怎么保证它真的对、别人会用?下一章讲 Rust 工程化的三件套:测试、错误处理、文档 —— 每一件都有让你惊喜的内建支持。