首页 / Rust 入门教程 / 包、crate 与模块系统

Rust 入门教程

包、crate 与模块系统

本教程共 78 篇 · 第 32 篇 · 更新于 2026-08-08 · 约 8 分钟阅读

RustRust 入门教程Packagecrate模块Cargo代码组织

本节目标:搞清楚 Package、crate、模块这三个概念分别是什么,以及它们如何一层层把代码组织起来。

你写的代码越来越多之后,把所有东西塞进一个文件会变得非常难维护。打开文件要翻半天,想找一个函数得像大海捞针,几个人一起改还会互相踩脚。现实里你不会把全部笔记写进同一个几十万字的文档,代码也一样。

Rust 提供了一套组织代码的机制,从大到小依次是:Package、crate、模块。这一章先把这三个概念讲明白,后面几章再逐个展开。

1-1 Package:一个完整的工程

Package(中文常译作”包”,也有人叫它”工程”或”项目”)是 Cargo 管理的最小单位。你在前面用 cargo new 创建出来的东西,就是一个 Package。

一个 Package 最重要的标志,是它拥有独立的 Cargo.toml 文件。这个文件记录了工程的名字、版本、依赖等信息。只要看到 Cargo.toml,就知道这是一个独立的工程。

一个 Package 里可以包含一个或多个 crate(注意这里的大小写:大写的 Package 是工程,小写的 crate 是编译单元,后面会细说)。具体规则是:

  • 最多只能有一个库类型的 crate;
  • 可以有任意多个二进制类型的 crate;
  • 也可以一个二进制 crate 都没有(纯库工程),或者只有一个二进制 crate(纯程序)。
Note

很多人把 Package 和 crate 混为一谈,因为它们常被一起提到。记住一句话就够:Package 是”工程文件夹”,crate 是”编译出来的东西”。工程包含编译单元,但两者不是一回事。

1-2 crate:可独立编译的单元

crate 是 Rust 里最小的、可以独立编译的代码单元。它编译后会变成两种东西之一:一个可执行文件(程序),或者一个库(给别人调用)。

你可以把 crate 理解成一盒打包好的功能。比如标准库之外常用的 rand crate,它把”生成随机数”这个功能打包好了。你只要把它引入自己的工程,就能用 rand::xxx 的方式调用里面的能力,完全不用关心它内部怎么实现。

crate 之间靠名字区分。同一个 crate 里不允许出现两个同名的类型,但不同 crate 之间可以有同名类型,编译器靠完整的路径把它们分清。比如 rand 里有个 Rng trait,你自己也能定义一个叫 Rng 的东西,前者通过 rand::Rng 访问,后者直接用 Rng,互不打架。

crate 有两种类型:

  • 二进制 crate:最终生成能直接运行的程序,入口是 src/main.rs 里的 fn main()
  • 库 crate:生成给别人当依赖用的库,入口是 src/lib.rs。它自己不能直接 cargo run
Tip

想快速判断一个 Package 是库还是程序?看 src 目录下是 lib.rs 还是 main.rs。有 main.rs 就能跑,有 lib.rs 就被人当零件用。

1-3 模块:代码的组织单位

crate 内部还不是一团散沙,它由一个个**模块(Module)**组成。模块是真正用来把功能相关代码收拢在一起的东西。

模块非常灵活:一个文件里可以放多个模块,也可以一个文件只放一个模块。模块里能定义函数、结构体、枚举、trait,还能再嵌套子模块。它就像是真实项目里的”文件夹”,把同类代码归置到一起,还顺手管住了”哪些东西能被外面看到”。

模块组成的是一棵模块树。树的根叫”包根(crate root)“,也就是 src/main.rssrc/lib.rs。从根往下,一层层嵌套出子模块,整棵树的样子和电脑里的文件夹目录非常像。

下面这个结构你之后会反复见到:

crate(包根)
 └── front_of_house(前厅)
     ├── hosting(接待)
     └── serving(服务)

1-4 为什么模块还能管”可见性”

模块不只是整理代码那么简单,它还画了一条”私有边界”。在这条边界内,你能精确控制:哪些东西允许外面访问,哪些只给自己人看。

Rust 出于安全考虑,规定模块里的所有项默认都是私有的——函数、方法、结构体、枚举、常量,甚至模块本身,统统默认不可见。这正是 Rust “默认安全”理念的体现:你想暴露什么,就显式地把什么打开。

Warning

初学者常在这里栽跟头:明明写了函数,调用时却报 “private” 错误。记住,Rust 不会自动把内部项公开,必须主动加 pub 关键字才行。这一点第 33 章会详细讲。

这种”默认私有、按需公开”的设计,让大型项目可以放心地把实现细节藏起来,只留一个干净的公共接口给别人用。接口稳定了,内部怎么重构都不影响外部调用者。

1-5 一个 Package 的典型长相

真实的 Rust 工程,目录结构大致是这样:

.
├── Cargo.toml
├── src
│   ├── main.rs        # 默认二进制 crate
│   ├── lib.rs         # 唯一的库 crate
│   └── bin
│       ├── tool1.rs   # 另一个二进制 crate
│       └── tool2.rs   # 又一个二进制 crate
├── tests              # 集成测试
├── benches           # 性能基准测试
└── examples          # 示例
  • src/lib.rs:整个工程里唯一的库 crate。
  • src/main.rs:默认二进制 crate,编译出的可执行文件名和 Package 同名。
  • src/bin/ 下的每个文件:各自是一个独立的二进制 crate。
  • 其余 testsbenchesexamples 目录,分别放测试、基准、示例。

这种布局在 GitHub 上绝大多数 Rust 项目里都能看到,是社区约定俗成的标准结构。

1-6 二进制 crate 与库 crate 的区别

一个 Package 里最常见的两种 crate 是”二进制 crate”和”库 crate”,它们的分工值得专门说清。

二进制 crate 的入口是 src/main.rs,编译后产出一个可以直接运行的程序。你一直能 cargo run,就是因为默认生成的就是这份入口。库 crate 的入口是 src/lib.rs,它编译出一个供别人依赖的库,本身不能单独运行——它的价值在于被其它 crate 通过 use 引入。

同一个 Package 可以同时拥有二者:把对外能力写在 src/lib.rs(及其下的模块),把”组装并启动”的逻辑写在 src/main.rs,让 main 去调用库。这样你的核心逻辑既能被命令行跑,也能被测试或其它项目复用。库的名字默认等于 Package 名,外部调用时写成 use 包名::某模块::某函数

Tip

想直接新建一个库工程,用 cargo new --lib 名字;想同时拥有两者,就在 src 下手动补上 lib.rsmain.rs 即可。

另外,Rust 约定把集成测试放在项目根目录的 tests/ 文件夹里,每个 .rs 文件会被当成独立的二进制 crate 编译,它们通过 use 包名::... 来调用你的库。这正好说明了”库 crate 对外、二进制 crate 对内”的分工:库负责能力,二进制负责把能力接上真实场景。

1-7 小结

把这一章的概念串起来:

  • Package 是工程,靠 Cargo.toml 定义,能包含若干 crate;
  • crate 是可编译单元,编译后是二进制程序或库;
  • 模块 是 crate 内部的组织单位,既能归置代码,又能控制可见性;
  • 所有模块组成一棵以包根为起点的模块树

下一章我们动手用 mod 关键字创建模块、控制作用域,把这套骨架真正搭起来。