编程语言研究书库

包、crate 与模块:代码的地图系统 — 第 7 章拆解

包、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:

  1. 所有项(函数、结构体、枚举、模块、常量)默认对父模块私有。 想藏一个函数?放进模块就行。
  2. 可见性方向不对称:父模块用不了子模块的私有项;子模块却能看见祖先模块的一切。 书里用餐厅打比方:后厨对顾客是私密的,但后厨的人看得见整个餐厅的运作。
  3. 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. 可带走的

  1. crate = 编译最小单元;package 至少一个 crate、至多一个库 crate。
  2. crate root(src/lib.rs 或 src/main.rs)是模块树的根。
  3. 绝对路径以 crate 或外部 crate 名开头;相对路径以 self/super/名字开头。
  4. 所有项默认私有;父看不见子的私有项,子看得见父的一切。
  5. pub mod 只公开模块本身,里面的项要各自 pub。
  6. pub struct 字段仍私有(需公开构造函数);pub enum 变体全公开。
  7. use 建快捷键,只在本作用域有效,也受隐私检查。
  8. 函数 use 到父模块、类型 use 到全路径;同名用父模块区分或 as 别名。
  9. pub use 再导出:内部结构和对外结构可以不同。
  10. 模块拆文件:mod foo; → src/foo.rs;子模块进子目录;不是 include。

10. 原文地图

主题原书章原文位置
crate 两种形态Chapter 7: Packages, Crates, and Modulestext/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」)

Footnotes

  1. 出处:「Chapter 7: Packages, Crates, and Modules」第 168 段(text/14-ch07-chapter-7-packages-crates-and-modules.txt:168,搜「tells the compiler to include」)。 ↩

  2. 出处:「Chapter 7: Packages, Crates, and Modules」第 54 段(text/14-ch07-chapter-7-packages-crates-and-modules.txt:54,搜「binary crate or a library crate」)。 ↩

  3. 出处:「Chapter 7: Packages, Crates, and Modules」第 66 段(text/14-ch07-chapter-7-packages-crates-and-modules.txt:66,搜「root module」)。 ↩

  4. 出处:「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」)。 ↩

  5. 出处:「Chapter 7: Packages, Crates, and Modules」第 92 段(text/14-ch07-chapter-7-packages-crates-and-modules.txt:92,搜「convention」)。 ↩

  6. 出处:「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」)。 ↩ ↩2

  7. 出处:「Chapter 7: Packages, Crates, and Modules」第 271 段(text/14-ch07-chapter-7-packages-crates-and-modules.txt:271,搜「absolute path」)。 ↩

  8. 出处:「Chapter 7: Packages, Crates, and Modules」第 327 段(text/14-ch07-chapter-7-packages-crates-and-modules.txt:327,搜「preference in general」)。 ↩ ↩2

  9. 出处:「Chapter 7: Packages, Crates, and Modules」第 499 段(text/14-ch07-chapter-7-packages-crates-and-modules.txt:499,搜「Starting Relative Paths with super」)。 ↩

  10. 出处:「Chapter 7: Packages, Crates, and Modules」第 370 段(text/14-ch07-chapter-7-packages-crates-and-modules.txt:370,搜「private to parent modules by default」)。 ↩

  11. 出处:「Chapter 7: Packages, Crates, and Modules」第 434 段(text/14-ch07-chapter-7-packages-crates-and-modules.txt:434,搜「doesn’t make its contents public」)。 ↩

  12. 出处:「Chapter 7: Packages, Crates, and Modules」第 380 段(text/14-ch07-chapter-7-packages-crates-and-modules.txt:380,搜「inner implementation details is the default」)。 ↩ ↩2

  13. 出处:「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」)。 ↩

  14. 出处:「Chapter 7: Packages, Crates, and Modules」第 589 段(text/14-ch07-chapter-7-packages-crates-and-modules.txt:589,搜「all of its variants」)。 ↩

  15. 出处:「Chapter 7: Packages, Crates, and Modules」第 474 段(text/14-ch07-chapter-7-packages-crates-and-modules.txt:474,搜「contract with users」)。 ↩

  16. 出处:「Chapter 7: Packages, Crates, and Modules」第 648 段(text/14-ch07-chapter-7-packages-crates-and-modules.txt:648,搜「only creates the shortcut」)。 ↩

  17. 出处:「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」)。 ↩ ↩2

  18. 出处:「Chapter 7: Packages, Crates, and Modules」第 763 段(text/14-ch07-chapter-7-packages-crates-and-modules.txt:763,搜「as Keyword」)。 ↩

  19. 出处:「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」)。 ↩

  20. 出处:「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」)。 ↩

  21. 出处:「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」)。 ↩

  22. 出处:「Chapter 7: Packages, Crates, and Modules」第 903 段(text/14-ch07-chapter-7-packages-crates-and-modules.txt:903,搜「Glob Operator」)。 ↩

  23. 出处:「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」)。 ↩

  24. 出处:「Chapter 7: Packages, Crates, and Modules」第 987 段(text/14-ch07-chapter-7-packages-crates-and-modules.txt:987,搜「ALTERNATE FILEPATHS」)。 ↩

On this page