包、crate 与模块:代码的地图系统¶
这一章讲什么: 项目大了之后,代码怎么摆、谁能看见谁。三个层级: crate(编译器一次编译的最小单元)、package(Cargo 管理的一组 crate)、 模块(crate 内部的命名空间)。读完你要能回答:为什么路径写对了还编译不过? 答案几乎总是"路径沿途有私有模块"。
1. 顶层全景:四个词,一棵树¶
package(一个 Cargo.toml 管的一组 crate)
└── crate(编译单元;至多 1 个库 crate + 任意个二进制 crate)
└── 模块树(以 crate root 为根:src/lib.rs 或 src/main.rs)
└── 每个模块:代码 + 隐私边界(默认私有,pub 开放)
图说:package 管 crate,crate 内长模块树,树上每条路径都要问"沿途 pub 了吗"。
开场例子是一个花园项目(backyard):crate 根 src/main.rs 里写 pub mod garden;,
src/garden.rs 里写 pub mod vegetables;,src/garden/vegetables.rs 里定义 pub struct Asparagus。
mod 声明出现在哪个模块里,就决定了它在树上的位置;编译器按"模块名对应文件名"的规则找代码1。
2. 定义层:crate 与 package¶
crate 是 Rust 编译器视为一个整体的最小代码量;哪怕只给 rustc 一个源文件,那个文件就是一个 crate。两种形态:
- 二进制 crate:能编译成可执行文件,必须有
main函数; - 库 crate:没有 main、不单独运行,供别的程序用。第 2 章的 rand 就是库 crate2。
crate root 是编译器开始读的那个源文件,同时它就是 crate 的根模块3。
package 是提供一个功能集合的一个或多个 crate 的捆绑,靠 Cargo.toml 描述怎么构建。 规则:一个 package 至少含一个 crate;二进制 crate 想有几个有几个,库 crate 至多一个。 Cargo 自己就是一个例子:命令行工具是二进制 crate,背后的构建逻辑是库 crate,别的项目可以依赖后者4。
cargo new 的惯例:默认建二进制 package(src/main.rs);cargo new --lib 建库 package(src/lib.rs)。Cargo.toml 里不会列出 src/main.rs——Cargo 走"目录约定":src/main.rs 是二进制 crate 根、src/lib.rs 是库 crate 根,都在则两者并存5。
双 crate package 的最佳实践(书里单列的方框):模块树写在 src/lib.rs 里,binary crate 只留"启动可执行文件、调用库代码"的薄壳。这样二进制 crate 就像外部用户一样只能走公共 API——你既是作者又是客户,API 设计会被迫变好6。
3. 模块树与路径¶
模块里可以再套模块;整棵树以隐式的 crate 为根。路径就是文件系统路径的翻版,两种写法:
- 绝对路径从 crate 根出发:当前 crate 内以字面量
crate开头;外部 crate 以 crate 名开头; - 相对路径从当前模块出发:以
self、super或当前模块里的名字开头7。
mod front_of_house {
mod hosting {
fn add_to_waitlist() {}
}
}
pub fn eat_at_restaurant() {
crate::front_of_house::hosting::add_to_waitlist(); // 绝对
front_of_house::hosting::add_to_waitlist(); // 相对
}
选哪个?书给了一条偏好:一般倾向绝对路径——因为"定义代码"和"调用代码"将来更可能各自搬家;绝对路径只随定义走,相对路径两边都牵连8。
super 开头则从父模块起跳(等价文件系统的 ..),适合"父子关系稳固、但父模块可能被挪走"的场景9。
4. 隐私规则:本章真正的引擎¶
上面那段代码编不过。报错 E0603:module hosting is private。路径本身没错,错在路径中途穿过了私有区域。
规则逐条拆开10:
- 所有项(函数、结构体、枚举、模块、常量)默认对父模块私有。 想藏一个函数?放进模块就行。
- 可见性方向不对称:父模块用不了子模块的私有项;子模块却能看见祖先模块的一切。 书里用餐厅打比方:后厨对顾客是私密的,但后厨的人看得见整个餐厅的运作。
pub逐个开放。 给mod hosting加 pub 只解决"能不能指着 hosting",不解决 hosting 里面的东西——还得再给fn add_to_waitlist加 pub,才编得过11。
设计上为什么默认私有?书点明:这样你知道内部哪些部分可以改而不破坏外部代码——封装不是限制,是变更自由的担保12。
pub 用在结构体/枚举上有细节差异:
pub struct只公开结构体本身,字段仍逐个私有;含私有字段的结构体必须提供公开的构造函数(例子里的Breakfast::summer),否则外部连实例都建不出来13。pub enum则所有变体自动公开——枚举的变体不公开就没法用,逐个标 pub 太烦,所以默认全开14。
公共 API 是对用户的合同;怎么管理它的演进超出本书范围,书指了 Rust API Guidelines15。
5. use:给路径起快捷键¶
use crate::front_of_house::hosting; 之后,作用域里就能直接写 hosting::add_to_waitlist()。效果类似建符号链接;use 也受隐私检查,且只在它所在的那个作用域有效——把函数挪进子模块,原来的 use 就不灵了,得把 use 也挪进去或在子模块写 super::hosting16。
惯用法有两条,书上明说第二条"没有强理由,就是约定俗成"17:
| 引进什么 | 惯用写法 | 为什么 |
|---|---|---|
| 函数 | 引进到父模块(use ...::hosting; 然后 hosting::add_to_waitlist()) |
调用时看得出"这不是本地函数",又不重复全路径 |
| 结构体/枚举等 | 引进完整路径(use std::collections::HashMap;) |
约定;两种都算地道,自选 |
同名冲突:std::fmt::Result 和 std::io::Result 不能同时以 Result 之名引进。两条出路——用父模块区分(fmt::Result / io::Result),或用 as 起别名(use std::io::Result as IoResult;)18。
pub use 再导出:use 引进的名字默认是私有的;加 pub 则对外重新导出——外部代码可以走更短的新路径。用处:内部实现按"前厅/后厨"组织,对外暴露按用户的领域概念组织;一套代码,两张地图19。
外部包:Cargo.toml 写依赖 + use rand::Rng; 两步。std 也是 crate,只是不用写进 Cargo.toml,use std::collections::HashMap; 以 std 开头的绝对路径20。
清理 use 列表:同前缀合并 use std::{cmp::Ordering, io};;子路径并入 use std::io::{self, Write};21。
glob use std::collections::*; 把路径下所有公开项引进来。书里的警告很具体:看不出名字从哪来;依赖升级若新增与你同名的定义,会直接编译错。常见于测试(use super::*;)和 prelude 模式22。
6. 模块拆进文件¶
mod front_of_house { ... } 的花括号内容可以挪走:根里只留 mod front_of_house;,编译器就去 src/front_of_house.rs 找。mod 在整个模块树里只声明一次;其他文件引用它,要用它在树上的位置路径——mod 不是 C 的 #include23。
子模块对应子目录:src/front_of_house.rs 里声明 pub mod hosting;,代码就要在 src/front_of_house/hosting.rs——目录结构镜像模块树。老式写法 src/front_of_house/mod.rs 仍支持,混用同一会报错;新写法避免了满工程 mod.rs 分不清24。
7. 作者的判断与证据¶
- "默认私有是为了让你知道哪些内部改动安全"——设计主张,封装换变更自由12。
- "绝对路径更好"——明确标注为偏好而非规则,理由是代码搬迁的独立性8。
- "库+二进制双 crate 的薄壳惯例"——惯例主张,理由是倒逼好 API6。
- "结构体全路径、函数到父模块"——明示第二条只是涌现的约定17。
判断(我们的,不是书里的): 这一章的机制有一个统一的精神:默认拒绝,显式开放 (私有默认、pub 逐层穿透、use 限作用域)。和变量的默认不可变、引用的默认只读是同一条曲线的 第三次出现——Rust 反复用"默认收紧 + 显式许可"换取"读者一眼看出哪里被放宽"。 学这一章时别只背规则,注意它和第 3、4 章的同构。 如果错,会错在: "默认私有"在小型单文件项目里是纯负担(多写 pub 而已); 它的收益随项目规模和作者人数上升——对一个人写的脚本,这套机制的性价比是负的。
8. 边界与局限¶
- 公共 API 的演进管理(semver 与 API 的联动)只指了外部指南,没展开。
- workspace(一组相关 package)只在章首提了一句,第 14 章才讲。
- glob 的问题给了警告但没给替代方案(除测试场景)。
9. 可带走的¶
- crate = 编译最小单元;package 至少一个 crate、至多一个库 crate。
- crate root(src/lib.rs 或 src/main.rs)是模块树的根。
- 绝对路径以
crate或外部 crate 名开头;相对路径以self/super/名字开头。 - 所有项默认私有;父看不见子的私有项,子看得见父的一切。
pub mod只公开模块本身,里面的项要各自 pub。pub struct字段仍私有(需公开构造函数);pub enum变体全公开。use建快捷键,只在本作用域有效,也受隐私检查。- 函数 use 到父模块、类型 use 到全路径;同名用父模块区分或
as别名。 pub use再导出:内部结构和对外结构可以不同。- 模块拆文件:
mod foo;→ src/foo.rs;子模块进子目录;不是 include。
10. 原文地图¶
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| crate 两种形态 | Chapter 7: Packages, Crates, and Modules | text/14-ch07-chapter-7-packages-crates-and-modules.txt:54(搜「binary crate or a library crate」) |
| package 定义与限制 | 同上 | text/14-ch07-chapter-7-packages-crates-and-modules.txt:68(搜「bundle of one or more crates」) · text/14-ch07-chapter-7-packages-crates-and-modules.txt:75(搜「only one library crate」) |
| crate root | 同上 | text/14-ch07-chapter-7-packages-crates-and-modules.txt:66(搜「root module」) |
| 花园示例 | 同上 | text/14-ch07-chapter-7-packages-crates-and-modules.txt:165(搜「I'm growing」) |
| 路径两种形式 | 同上 | text/14-ch07-chapter-7-packages-crates-and-modules.txt:271(搜「absolute path」) |
| 绝对路径偏好 | 同上 | text/14-ch07-chapter-7-packages-crates-and-modules.txt:327(搜「preference in general」) |
| E0603 与隐私方向 | 同上 | text/14-ch07-chapter-7-packages-crates-and-modules.txt:335(搜「module hosting is private」) · text/14-ch07-chapter-7-packages-crates-and-modules.txt:370(搜「private to parent modules by default」) |
| pub 逐层穿透 | 同上 | text/14-ch07-chapter-7-packages-crates-and-modules.txt:434(搜「doesn’t make its contents public」) |
| 结构体 vs 枚举的 pub | 同上 | text/14-ch07-chapter-7-packages-crates-and-modules.txt:539(搜「still be private」) · text/14-ch07-chapter-7-packages-crates-and-modules.txt:589(搜「all of its variants」) |
| 双 crate 最佳实践 | 同上 | text/14-ch07-chapter-7-packages-crates-and-modules.txt:480(搜「BES T PR AC TICES」) |
| use 的作用域限制 | 同上 | text/14-ch07-chapter-7-packages-crates-and-modules.txt:648(搜「only creates the shortcut」) |
| 惯用 use 路径 | 同上 | text/14-ch07-chapter-7-packages-crates-and-modules.txt:716(搜「idiomatic」) |
| as / pub use / glob | 同上 | text/14-ch07-chapter-7-packages-crates-and-modules.txt:763(搜「as Keyword」) · text/14-ch07-chapter-7-packages-crates-and-modules.txt:787(搜「Re-exporting」) · text/14-ch07-chapter-7-packages-crates-and-modules.txt:903(搜「Glob」) |
| mod 拆文件 | 同上 | text/14-ch07-chapter-7-packages-crates-and-modules.txt:960(搜「your module tree」) · text/14-ch07-chapter-7-packages-crates-and-modules.txt:987(搜「ALTERNATE FILEPATHS」) |
-
出处:「Chapter 7: Packages, Crates, and Modules」第 168 段(
text/14-ch07-chapter-7-packages-crates-and-modules.txt:168,搜「tells the compiler to include」)。 ↩ -
出处:「Chapter 7: Packages, Crates, and Modules」第 54 段(
text/14-ch07-chapter-7-packages-crates-and-modules.txt:54,搜「binary crate or a library crate」)。 ↩ -
出处:「Chapter 7: Packages, Crates, and Modules」第 66 段(
text/14-ch07-chapter-7-packages-crates-and-modules.txt:66,搜「root module」)。 ↩ -
出处:「Chapter 7: Packages, Crates, and Modules」第 68 段(
text/14-ch07-chapter-7-packages-crates-and-modules.txt:68,搜「bundle of one or more crates」)与第 75 段(text/14-ch07-chapter-7-packages-crates-and-modules.txt:75,搜「only one library crate」)。 ↩ -
出处:「Chapter 7: Packages, Crates, and Modules」第 92 段(
text/14-ch07-chapter-7-packages-crates-and-modules.txt:92,搜「convention」)。 ↩ -
出处:「Chapter 7: Packages, Crates, and Modules」第 480 段(
text/14-ch07-chapter-7-packages-crates-and-modules.txt:480,搜「BES T PR AC TICES」)与第 495 段(text/14-ch07-chapter-7-packages-crates-and-modules.txt:495,搜「you’re also a client」)。 ↩↩ -
出处:「Chapter 7: Packages, Crates, and Modules」第 271 段(
text/14-ch07-chapter-7-packages-crates-and-modules.txt:271,搜「absolute path」)。 ↩ -
出处:「Chapter 7: Packages, Crates, and Modules」第 327 段(
text/14-ch07-chapter-7-packages-crates-and-modules.txt:327,搜「preference in general」)。 ↩↩ -
出处:「Chapter 7: Packages, Crates, and Modules」第 499 段(
text/14-ch07-chapter-7-packages-crates-and-modules.txt:499,搜「Starting Relative Paths with super」)。 ↩ -
出处:「Chapter 7: Packages, Crates, and Modules」第 370 段(
text/14-ch07-chapter-7-packages-crates-and-modules.txt:370,搜「private to parent modules by default」)。 ↩ -
出处:「Chapter 7: Packages, Crates, and Modules」第 434 段(
text/14-ch07-chapter-7-packages-crates-and-modules.txt:434,搜「doesn’t make its contents public」)。 ↩ -
出处:「Chapter 7: Packages, Crates, and Modules」第 380 段(
text/14-ch07-chapter-7-packages-crates-and-modules.txt:380,搜「inner implementation details is the default」)。 ↩↩ -
出处:「Chapter 7: Packages, Crates, and Modules」第 539 段(
text/14-ch07-chapter-7-packages-crates-and-modules.txt:539,搜「still be private」)与第 584 段(text/14-ch07-chapter-7-packages-crates-and-modules.txt:584,搜「public associated function」)。 ↩ -
出处:「Chapter 7: Packages, Crates, and Modules」第 589 段(
text/14-ch07-chapter-7-packages-crates-and-modules.txt:589,搜「all of its variants」)。 ↩ -
出处:「Chapter 7: Packages, Crates, and Modules」第 474 段(
text/14-ch07-chapter-7-packages-crates-and-modules.txt:474,搜「contract with users」)。 ↩ -
出处:「Chapter 7: Packages, Crates, and Modules」第 648 段(
text/14-ch07-chapter-7-packages-crates-and-modules.txt:648,搜「only creates the shortcut」)。 ↩ -
出处:「Chapter 7: Packages, Crates, and Modules」第 716 段(
text/14-ch07-chapter-7-packages-crates-and-modules.txt:716,搜「idiomatic way」)与第 736 段(text/14-ch07-chapter-7-packages-crates-and-modules.txt:736,搜「no strong reason」)。 ↩↩ -
出处:「Chapter 7: Packages, Crates, and Modules」第 763 段(
text/14-ch07-chapter-7-packages-crates-and-modules.txt:763,搜「as Keyword」)。 ↩ -
出处:「Chapter 7: Packages, Crates, and Modules」第 787 段(
text/14-ch07-chapter-7-packages-crates-and-modules.txt:787,搜「Re-exporting」)与第 823 段(text/14-ch07-chapter-7-packages-crates-and-modules.txt:823,搜「different structure」)。 ↩ -
出处:「Chapter 7: Packages, Crates, and Modules」第 835 段(
text/14-ch07-chapter-7-packages-crates-and-modules.txt:835,搜「download the」)与第 853 段(text/14-ch07-chapter-7-packages-crates-and-modules.txt:853,搜「library is also a crate」)。 ↩ -
出处:「Chapter 7: Packages, Crates, and Modules」第 872 段(
text/14-ch07-chapter-7-packages-crates-and-modules.txt:872,搜「nested paths」)与第 897 段(text/14-ch07-chapter-7-packages-crates-and-modules.txt:897,搜「self, Write」)。 ↩ -
出处:「Chapter 7: Packages, Crates, and Modules」第 903 段(
text/14-ch07-chapter-7-packages-crates-and-modules.txt:903,搜「Glob Operator」)。 ↩ -
出处:「Chapter 7: Packages, Crates, and Modules」第 960 段(
text/14-ch07-chapter-7-packages-crates-and-modules.txt:960,搜「your module tree」)与第 965 段(text/14-ch07-chapter-7-packages-crates-and-modules.txt:965,搜「include」)。 ↩ -
出处:「Chapter 7: Packages, Crates, and Modules」第 987 段(
text/14-ch07-chapter-7-packages-crates-and-modules.txt:987,搜「ALTERNATE FILEPATHS」)。 ↩