第 10 章 模块系统
第 10 章 模块系统(Modules)
“想象一下,如果你把所有的衣服都堆在一个大箱子里,找衣服的时候估计得翻遍整个箱子。Rust 的模块系统就是你的衣柜整理神器——把代码分类放好,想用什么就精准拿什么,再也不用翻箱倒柜了!”
在写代码这条不归路上,我们迟早会遇到一个灵魂拷问:代码太多了,怎么组织?
如果你把所有代码都塞进一个文件里,恭喜你,你获得了一个"史诗级巨无霸文件"——这个文件可能比你前任的道歉信还要长,查找起来比在沙漠里找针还难。
Rust 的模块系统就是来解决这个问题的。它让你把代码像整理衣柜一样分类存放:上衣放一格、裤子放一格、袜子放一格(虽然袜子可能永远是那只找不到的那只)。
这一章,我们就来学习怎么把 Rust 代码整理得井井有条,让你的代码库从"垃圾堆"升级成"样板间"!
10.1 模块基础
10.1.1 Crate 与 Package 的概念区分
10.1.1.1 Crate:编译单元(lib.rs / main.rs / bin/*.rs)
Crate 是 Rust 中的基本编译单元。你可以把它理解为一个"代码包"——当你运行 rustc 编译一个 .rs 文件时,那个文件及其所有 mod 引入的模块,就共同组成了一个 Crate。
1
2
| # 编译单个文件,产生一个 Crate
rustc main.rs -o main
|
在 Rust 的世界里,Crate 有两种主要类型:
- 二进制 Crate(Binary Crate):可以被编译成可执行文件,有
main 函数作为入口 - 库 Crate(Library Crate):被其他 Crate 依赖使用的代码集合,没有
main 函数
1
2
3
4
5
6
| // 这是二进制 Crate 的入口
// 文件名通常是 main.rs 或者 src/bin/*.rs
fn main() {
println!("我是可执行文件!"); // 我是可执行文件!
}
|
1
2
3
4
5
6
| // 这是库 Crate 的入口
// 文件名通常是 lib.rs
pub fn hello() {
println!("我是库!"); // 我是库!
}
|
10.1.1.2 Package:Cargo.toml 定义的包(可包含多个 crate)
Package 是由 Cargo.toml 定义的单位。一个 Package 可以包含一个或多个 Crate。
1
2
3
4
5
6
7
8
9
10
11
| # Cargo.toml
[package]
name = "my-awesome-project" # 包的名字
version = "0.1.0" # 版本号
edition = "2021" # Rust 版本
authors = ["You <you@example.com>"]
# 这个 Package 包含:
# 1. 一个二进制 Crate(src/main.rs)
# 2. 一个库 Crate(src/lib.rs)
# 3. 多个二进制 Crate(src/bin/*.rs)
|
当你运行 cargo new project_name 时,Cargo 会自动创建一个 Package,里面包含一个基础的二进制 Crate。
10.1.1.3 lib.rs / main.rs 的区别(库 vs 二进制入口)
这是 Rust 项目里最经典的一对"欢喜冤家":
| 文件 | 作用 | 能否被其他 Crate 依赖 |
|---|
lib.rs | 库 Crate 的入口 | ✅ 可以 |
main.rs | 二进制 Crate 的入口 | ❌ 不行 |
1
2
3
4
5
6
7
8
9
| # 项目结构示例
my_project/
├── Cargo.toml # Package 描述文件
├── src/
│ ├── main.rs # 二进制入口
│ ├── lib.rs # 库入口
│ └── bin/
│ ├── tool_a.rs # 额外的二进制 Crate
│ └── tool_b.rs # 另一个额外的二进制 Crate
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| // src/lib.rs
// 这是库的"广告牌",在这里 pub 的东西可以被外部访问
pub mod utils;
pub mod models;
pub fn public_function() {
println!("库里的公开函数!"); // 库里的公开函数!
}
/// 只有在文档注释里的内容才会出现在 cargo doc 中
pub fn documented_function() {
internal_function(); // 内部函数也可以调用
}
fn internal_function() {
println!("我是内部函数,外面看不到我"); // 我是内部函数,外面看不到我
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
| // src/main.rs
// 这是程序的主入口
use my_awesome_project::utils;
use my_awesome_project::models;
fn main() {
// 调用库里的公开函数
utils::helper(); // 调用了工具函数!
let model = models::User::new(String::from("Alice"));
println!("{:?}", model); // User { name: "Alice" }
}
|
小贴士:如果你不确定该用 lib.rs 还是 main.rs,记住这个原则:你想让其他人用的代码放 lib.rs,只是你自己运行的代码放 main.rs。库是分享给世界的,程序是给自己跑的。
10.1.2 mod 声明与模块树
10.1.2.1 mod foo 声明模块(查找 foo.rs 或 foo/mod.rs)
在 Rust 里,mod 关键字是用来声明模块的。当你写 mod foo; 时,Rust 编译器会做两件事:
- 在当前目录查找
foo.rs 文件 - 如果没找到,查找
foo/ 目录下的 mod.rs 文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| // src/main.rs
// 声明一个叫 foo 的模块
// Rust 会去找 src/foo.rs 或者 src/foo/mod.rs
mod foo;
// 也可以声明嵌套模块
mod outer {
pub mod inner {
pub fn inner_function() {
println!("我是 inner 模块的函数!"); // 我是 inner 模块的函数!
}
}
pub fn outer_function() {
println!("我是 outer 模块的函数!"); // 我是 outer 模块的函数!
}
}
fn main() {
// 调用模块里的函数
foo::hello();
// 调用嵌套模块的函数
outer::outer_function();
outer::inner::inner_function();
}
|
1
2
3
4
5
6
| // src/foo.rs
// 这是 foo 模块的定义
pub fn hello() {
println!("Hello from foo module!"); // Hello from foo module!
}
|
10.1.2.2 模块树结构(root → crate → 模块层层嵌套)
Rust 的模块系统是一棵树,根节点是你的 Crate(lib.rs 或 main.rs),所有的模块都是这棵树上的分支。
graph TD
A[crate root<br/>lib.rs / main.rs] --> B[模块: utils]
A --> C[模块: models]
A --> D[模块: api]
B --> B1[子模块: math]
B --> B2[子模块: string]
D --> D1[子模块: routes]
D --> D2[子模块: handlers] 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| // 假设我们有这样一个项目结构:
// src/
// ├── main.rs (crate root)
// ├── utils.rs
// ├── models.rs
// ├── api/
// │ ├── mod.rs
// │ ├── routes.rs
// │ └── handlers.rs
// src/main.rs
mod utils; // 加载 src/utils.rs
mod models; // 加载 src/models.rs
mod api; // 加载 src/api/mod.rs
fn main() {
// utils::add(1, 2)
// models::User::new()
// api::routes::setup()
}
|
10.1.2.3 父模块与子模块(crate 是根模块)
每个模块都可以有子模块,子模块可以有自己的子模块。父模块能看到子模块里 pub 的内容,但子模块看不到父模块里的私有内容——这很合理,总不能让儿子随便翻爸爸的抽屉吧。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| // 父模块
mod parent {
pub fn public() {
println!("我是公开的!"); // 我是公开的!
// 子模块可以从这里访问
}
fn private() {
println!("我是私有的,谁也别想访问我!"); // 我是私有的,谁也别想访问我!
}
// 子模块
pub mod child {
pub fn child_function() {
// child 可以调用 parent 的公开函数
super::public(); // super 指向父模块
println!("子模块在喊爸爸!"); // 子模块在喊爸爸!
}
}
}
fn main() {
parent::public(); // OK
parent::child::child_function(); // OK
// parent::private(); // 错误!private 函数不能从外部访问
}
|
super 关键字:在子模块里,super 就像在说"我爸刚才说……",它允许你访问父模块的内容。这是模块间通信的"后门"。
10.1.3 文件系统与模块布局
10.1.3.1 现代布局(Rust 2018+):foo.rs(叶子模块)
从 Rust 2018 开始,模块布局变得更简洁了。你可以直接用 foo.rs 来定义叶子模块,不需要再搞 foo/mod.rs 这种中间商模式。
1
2
3
4
5
6
| # 现代布局(Rust 2018+)
src/
├── main.rs
├── utils.rs # mod utils; 加载这个文件
├── models.rs # mod models; 加载这个文件
└── network.rs # mod network; 加载这个文件
|
1
2
3
4
5
6
| // src/utils.rs
// 直接定义 utils 模块,不需要子目录
pub fn help() {
println!("帮助信息"); // 帮助信息
}
|
1
2
3
4
5
6
| // src/main.rs
mod utils; // 加载 src/utils.rs
fn main() {
utils::help(); // 帮助信息
}
|
10.1.3.2 目录模块:foo/mod.rs(目录模块的入口)
当你有一个模块需要包含子模块时,可以使用目录结构:
1
2
3
4
5
6
7
8
| # 目录模块布局
src/
├── main.rs
├── network/
│ ├── mod.rs # 入口文件,定义 network 模块
│ ├── tcp.rs # network::tcp 子模块
│ ├── udp.rs # network::udp 子模块
│ └── http.rs # network::http 子模块
|
1
2
3
4
5
6
7
8
| // src/network/mod.rs
pub mod tcp;
pub mod udp;
pub mod http;
pub fn connect() {
println!("建立网络连接!"); // 建立网络连接!
}
|
1
2
3
4
| // src/network/tcp.rs
pub fn create_tcp_socket() {
println!("创建 TCP 套接字!"); // 创建 TCP 套接字!
}
|
1
2
3
4
| // src/network/udp.rs
pub fn create_udp_socket() {
println!("创建 UDP 套接字!"); // 创建 UDP 套接字!
}
|
1
2
3
4
5
6
7
8
| // src/main.rs
mod network;
fn main() {
network::connect();
network::tcp::create_tcp_socket();
network::udp::create_udp_socket();
}
|
10.1.3.3 两种布局的选用(叶子模块用 .rs / 有子模块用目录)
选择恐惧症的福音来了!
原则很简单:
- 如果一个模块没有子模块,用
.rs 文件 → foo.rs - 如果一个模块有子模块,用目录 →
foo/mod.rs + foo/*.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # 混合布局示例
src/
├── main.rs
├── utils.rs # 叶子模块,没子模块,用 .rs
├── config.rs # 叶子模块,没子模块,用 .rs
│
├── api/ # 有子模块,用目录
│ ├── mod.rs
│ ├── routes.rs
│ └── handlers.rs
│
└── db/ # 有子模块,用目录
├── mod.rs
├── connection.rs
└── query.rs
|
实战建议:在现代 Rust 项目中,建议优先使用 foo.rs 布局(没有子模块的情况下)。它更简洁,文件导航也更方便。只有当你确实需要组织子模块时,才使用目录布局。
10.1.4 use 语句与路径
10.1.4.1 use crate::module::Item(绝对路径,从 crate root 开始)
在 Rust 里,use 关键字就像给你的代码发了一张"快速通行证",让你不用每次都写完整的路径。
绝对路径从你的 Crate 根开始,用 crate:: 开头:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| // src/lib.rs
pub mod utils {
pub mod math {
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
}
}
pub struct User {
pub name: String,
}
// src/main.rs
use crate::utils::math::add; // 从 crate 根开始找
use crate::User;
fn main() {
let result = add(1, 2);
println!("1 + 2 = {}", result); // 1 + 2 = 3
let user = User { name: String::from("Bob") };
println!("用户:{}", user.name); // 用户:Bob
}
|
10.1.4.2 use super::Item(相对路径,从父模块开始)
相对路径从当前模块的位置开始:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| mod outer {
pub mod inner {
pub fn inner_fn() {
println!("inner 函数被调用了!"); // inner 函数被调用了!
}
}
pub fn outer_fn() {
// outer_fn 在 outer 模块里,inner 是 outer 的子模块
// 同级子模块可以直接通过模块名访问,不需要 super
inner::inner_fn();
println!("outer 函数被调用了!"); // outer 函数被调用了!
}
}
fn main() {
outer::outer_fn();
}
|
10.1.4.3 self 模块引用:use self::module::Item
self 关键字代表当前模块,可以简化导入路径:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| mod my_module {
pub const VALUE: i32 = 42;
pub mod nested {
pub fn nested_fn() {
println!("nested 函数!"); // nested 函数!
}
}
// 使用 self 来引用当前模块
pub use self::nested::nested_fn;
}
fn main() {
// 直接从 my_module 访问 VALUE
println!("{}", my_module::VALUE); // 42
// 通过 pub use 导入的 nested_fn
my_module::nested_fn(); // nested 函数!
}
|
10.1.4.4 pub use(re-export,公开子模块内容)
pub use 是一个非常有意思的关键字组合——重新导出。它的作用是把一个模块的内容"暴露"出去,即使这些内容原本不在这个模块里。
1
2
3
4
5
6
7
8
9
10
11
| // src/lib.rs
pub mod inner {
pub mod deep {
pub fn secret_function() {
println!("这是一个深层秘密函数!"); // 这是一个深层秘密函数!
}
}
}
// 把深层模块公开化
pub use inner::deep::secret_function;
|
外部使用:
1
2
3
4
5
6
7
| // 外部可以直接用顶级模块访问深层函数
// 不需要层层嵌套:my_crate::inner::deep::secret_function()
use my_crate::secret_function;
fn main() {
secret_function(); // 这是一个深层秘密函数!
}
|
实际应用场景:在编写库的时候,pub use 可以帮你设计一个更友好的公共 API,把实现细节藏在层层模块里,但对外提供一个简洁的接口。这就好比餐厅的菜单——你不需要知道厨房里有多少个锅,菜单上写着"宫保鸡丁"就够了。
10.1.4.5 use Item::{A, B, C}(大括号枚举导入)
当你需要从同一个模块导入多个项时,可以使用大括号来批量导入:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| mod utils {
pub fn add(a: i32, b: i32) -> i32 { a + b }
pub fn sub(a: i32, b: i32) -> i32 { a - b }
pub fn mul(a: i32, b: i32) -> i32 { a * b }
pub fn div(a: i32, b: i32) -> i32 { a / b }
}
// 从 utils 模块导入多个函数
use utils::{add, sub, mul, div};
fn main() {
let a = 10;
let b = 3;
println!("{} + {} = {}", a, b, add(a, b)); // 10 + 3 = 13
println!("{} - {} = {}", a, b, sub(a, b)); // 10 - 3 = 7
println!("{} * {} = {}", a, b, mul(a, b)); // 10 * 3 = 30
println!("{} / {} = {}", a, b, div(a, b)); // 10 / 3 = 3
}
|
10.1.4.6 use Item::*( glob 导入,不推荐)
Glob 导入(*)可以导入一个模块里的所有公开项,但是——
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| // 警告:glob 导入在大型代码库中可能导致命名冲突
// 不推荐在大项目中使用
mod helpers {
pub fn say_hi() { println!("Hi!"); }
pub fn say_bye() { println!("Bye!"); }
pub const MAX: i32 = 100;
}
use helpers::*;
fn main() {
say_hi(); // Hi!
say_bye(); // Bye!
println!("MAX = {}", MAX); // MAX = 100
}
|
警告:glob 导入虽然方便,但它是一个"隐形的炸弹"。如果两个模块恰好有同名的函数或常量,编译器就会报"命名冲突"错误。所以,在正式项目中,尽量使用精确的导入。
10.1.5 可见性(Visibility)
10.1.5.1 默认私有(private)
在 Rust 中,所有内容默认都是私有的。这是 Rust 的"最小暴露原则"——你不想让外人看到的东西,就藏得严严实实的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| mod internal {
fn secret_function() {
println!("我是私有函数!"); // 我是私有函数!
}
pub fn public_function() {
println!("我是公开函数!"); // 我是公开函数!
secret_function(); // 同模块内可以访问私有函数
}
}
fn main() {
internal::public_function(); // OK
// internal::secret_function(); // 错误!私有函数不能从外部访问
}
|
10.1.5.2 pub(公开)
pub 关键字让一个项对所有地方都可见:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| mod public_mod {
pub struct Secret {
pub name: String, // 公开字段
pub age: u32, // 公开字段
secret: String, // 私有字段,外部无法访问
}
impl Secret {
pub fn new(name: &str, age: u32) -> Self {
Secret {
name: name.to_string(),
age,
secret: String::from("[私有信息]"),
}
}
// 只能通过这种方法访问私有字段
pub fn reveal_secret(&self) -> &str {
&self.secret
}
}
}
fn main() {
let s = Secret::new("张三", 25);
println!("名字:{}", s.name); // 名字:张三
println!("年龄:{}", s.age); // 年龄:25
// println!("秘密:{}", s.secret); // 错误!secret 是私有的
println!("秘密:{}", s.reveal_secret()); // 秘密:[私有信息]
}
|
10.1.5.3 pub(crate)(crate 内可见)
pub(crate) 表示"在这个 crate 内部随便用,但外部 crate 休想看到":
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| // src/lib.rs
pub mod outer {
pub(crate) fn crate_internal() {
println!("crate 内部都可以调用我!"); // crate 内部都可以调用我!
}
pub fn public() {
println!("公开函数!"); // 公开函数!
}
}
fn main() {
outer::crate_internal(); // OK:main.rs 和 lib.rs 属于同一个 crate
outer::public();
}
|
10.1.5.4 pub(super)(父模块可见)
pub(super) 表示"只能在我爸那个模块(父模块)里用":
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| mod parent {
pub mod child {
pub(super) fn child_only_parent_can_see() {
println!("只有爸爸能看到我!"); // 只有爸爸能看到我!
}
pub fn child_everyone_can_see() {
println!("所有人都能看到我!"); // 所有人都能看到我!
// 在子模块里调用父模块可见的函数
super::parent_function();
}
}
pub fn parent_function() {
// 父模块可以看到 child 的 pub(super) 函数
child::child_only_parent_can_see();
}
}
fn main() {
parent::parent_function();
parent::child::child_everyone_can_see();
// parent::child::child_only_parent_can_see(); // 错误!外部访问不了
}
|
10.1.5.5 pub(in path)(指定路径可见)
pub(in path) 是最精细的控制粒度——你可以指定"只有 path 路径下的模块能访问":
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| mod a {
pub mod b {
pub(in crate::a) fn a_b_only_in_a() {
println!("在 crate::a 范围内都可以访问我!"); // 在 crate::a 范围内都可以访问我!
}
pub mod c {
pub fn c_function() {
// c 是 a::b::c,在 a 的范围内,可以访问 a_b_only_in_a
super::a_b_only_in_a();
println!("c 模块的函数!"); // c 模块的函数!
}
}
}
pub fn a_function() {
// a 可以访问 b 的 pub(in crate::a) 函数
b::a_b_only_in_a();
println!("a 模块的函数!"); // a 模块的函数!
}
}
fn main() {
a::a_function();
a::b::c::c_function();
// a::b::a_b_only_in_a(); // 错误!main 不在 crate::a 范围内
}
|
总结:Rust 的可见性系统就像一层层递进的安保系统:
pub = 向全世界公开pub(crate) = 只在当前 crate 内公开pub(super) = 只在父模块公开pub(in path) = 只在指定路径内公开- 默认(无关键字)= 只能在当前模块内访问
10.2 包与工作空间
10.2.1 Cargo.toml 结构详解
10.2.1.1 [package] 元信息(name / version / edition / authors / description)
[package] 是 Cargo.toml 的"个人资料卡":
1
2
3
4
5
6
7
8
9
10
11
| [package]
name = "my-awesome-project" # 包的名字,全宇宙唯一
version = "0.1.0" # 语义化版本号:主版本.次版本.修订号
edition = "2021" # Rust 版本,目前推荐 2021
authors = ["张三 <zhangsan@example.com>", "李四 <lisi@example.com>"]
description = "这是一个超级酷的项目!" # 一句话描述
license = "MIT" # 开源许可证
repository = "https://github.com/example/my-awesome-project" # 代码仓库
keywords = ["utils", "helper", "tool"] # 关键字,方便在 crates.io 搜索
categories = ["development-tools"] # 分类
rust-version = "1.70" # 最低支持的 Rust 版本
|
10.2.1.2 [dependencies] / [dev-dependencies] / [build-dependencies]
三兄弟,各司其职:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| [dependencies]
# 正式依赖:程序运行和测试都需要
serde = "1.0" # 序列化库
tokio = { version = "1.0", features = ["full"] } # 异步运行时
log = "0.4"
[dev-dependencies]
# 开发依赖:仅在 cargo test 时使用
criterion = "0.5" # 性能基准测试
proptest = "1.0" # 属性测试
tempfile = "3.0" # 临时文件
[build-dependencies]
# 构建依赖:仅在 build.rs 脚本中使用
quote = "1.0"
syn = "2.0"
|
1
2
3
4
5
6
| // build.rs 示例
fn main() {
// build-dependencies 里的依赖只能在这里使用
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=src/schema.rs");
}
|
10.2.1.3 [features] 条件编译特性(可选特性)
[features] 让你可以"功能模块化",用户可以根据需要启用或禁用某些功能:
1
2
3
4
5
6
| [features]
default = ["default-utils"] # 默认启用的特性
default-utils = [] # 默认工具库
experimental-async = ["tokio/async"] # 实验性异步功能
ssl-support = ["native-tls"] # SSL 支持
full-features = ["experimental-async", "ssl-support"] # 全功能模式
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| // 在代码中根据特性条件编译
#[cfg(feature = "experimental-async")]
pub async fn experimental_function() {
println!("这是实验性异步功能!"); // 这是实验性异步功能!
}
#[cfg(not(feature = "experimental-async"))]
pub fn experimental_function() {
println!("这是同步版本的实验功能(因为没开 experimental-async)");
}
#[cfg(feature = "ssl-support")]
pub fn secure_connect() {
println!("启用 SSL 安全连接!"); // 启用 SSL 安全连接!
}
fn main() {
experimental_function();
#[cfg(feature = "ssl-support")]
secure_connect();
}
|
运行方式:
1
2
3
4
5
6
7
8
| # 默认编译(启用 default features)
cargo build
# 启用 experimental-async
cargo build --features experimental-async
# 启用多个 features
cargo build --features "experimental-async ssl-support"
|
10.2.1.4 [profile] 编译配置(opt-level / lto / panic)
[profile] 让你控制 Rust 编译器的行为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| [profile.dev] # 开发模式
opt-level = 0 # 不优化(编译快)
debug = true # 包含调试信息
[profile.release] # 发布模式
opt-level = 3 # 最高优化级别
lto = true # 链接时优化(Link Time Optimization)
codegen-units = 1 # 减少并行编译单元数,提高优化效果
strip = true # 移除符号表(减小二进制体积)
panic = "abort" # panic 时直接 abort(减小体积)
[profile.test] # 测试模式(默认继承 profile.dev 的配置)
# opt-level = 0 # 继承自 dev,无需重复写
# debug = true # 继承自 dev,无需重复写
|
10.2.1.5 [lib] 库配置(crate 名称 / 路径)
当你的 Package 包含库 Crate 时,用 [lib] 配置它:
1
2
3
4
| [lib]
name = "mylib" # 库 Crate 的名字(影响依赖时的引用名称)
crate-type = ["lib", "cdylib"] # 产生的 Crate 类型
path = "src/lib.rs" # 库入口文件路径
|
crate-type 可以是:
lib:静态库cdylib:C 兼容的动态库(用于 FFI)rlib:Rust 专用库格式
10.2.1.6 [[bin]] 多二进制配置
如果你的项目有多个可执行文件,可以用 [[bin]] 配置:
1
2
3
4
5
6
7
8
| [[bin]]
name = "my-app" # 可执行文件名
path = "src/main.rs" # 入口文件
required-features = ["full"] # 需要的 features
[[bin]]
name = "my-tool"
path = "src/bin/tool.rs"
|
10.2.2 多二进制 Crate
10.2.2.1 [[bin]] 配置(name / path / required-features)
当你有多个二进制入口时:
1
2
3
4
5
6
7
8
9
10
11
12
13
| # Cargo.toml
[[bin]]
name = "server"
path = "src/bin/server.rs"
required-features = ["server"]
[[bin]]
name = "client"
path = "src/bin/client.rs"
[[bin]]
name = "benchmark"
path = "src/bin/benchmark.rs"
|
10.2.2.2 多个入口文件(src/bin/*.rs)
更简单的方式:直接把文件放到 src/bin/ 目录下,Cargo 会自动识别它们:
1
2
3
4
5
6
| src/
├── main.rs
└── bin/
├── server.rs # 可执行文件:cargo run --bin server
├── client.rs # 可执行文件:cargo run --bin client
└── bench.rs # 可执行文件:cargo run --bin bench
|
1
2
3
4
| // src/bin/server.rs
fn main() {
println!("服务器启动中..."); // 服务器启动中...
}
|
1
2
3
4
| // src/bin/client.rs
fn main() {
println!("客户端连接中..."); // 客户端连接中...
}
|
1
2
3
4
| # 运行不同的二进制
cargo run --bin server # 运行 server
cargo run --bin client # 运行 client
cargo build --bins # 构建所有二进制
|
10.2.2.3 二进制命名
二进制文件的名字由 name 字段决定(在 [[bin]] 中指定),或者默认使用文件名。
1
2
3
| [[bin]]
name = "awesome-tool" # 构建出的可执行文件叫 awesome-tool(不是 server!)
path = "src/bin/server.rs"
|
10.2.3 库 Crate
10.2.3.1 [lib] 配置
1
2
3
4
| [lib]
name = "mylib" # 库的名字
path = "src/lib.rs" # 库入口
crate-type = ["lib"] # 产生的库类型
|
10.2.3.2 lib.rs 入口点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| // src/lib.rs
// 这是库的根模块
pub mod utils;
pub mod models;
pub fn public_api() {
utils::helper();
println!("这是库的公开 API!"); // 这是库的公开 API!
}
fn private_api() {
println!("这是库的私有实现!"); // 这是库的私有实现!
}
|
10.2.3.3 库与二进制共存(同一 package 包含 lib + binary)
这是最常见的项目结构——一个 Package 同时包含库和二进制:
1
2
3
4
5
| my_project/
├── Cargo.toml
└── src/
├── main.rs # 二进制入口
└── lib.rs # 库入口
|
1
2
3
4
| // src/lib.rs
pub fn library_function() {
println!("来自库的功能!"); // 来自库的功能!
}
|
1
2
3
4
5
6
7
| // src/main.rs
// 使用自己的库
use my_project::library_function;
fn main() {
library_function(); // 来自库的功能!
}
|
10.2.4 Cargo 工作空间(Workspace)
10.2.4.1 [workspace] 配置(root package / members)
工作空间让你管理多个相关的 Package。想象你有一个"巨型项目",它由多个独立的小项目组成,工作空间就是把这些小项目组织在一起的管理者。
1
2
3
4
| # Cargo.toml(根目录)
[workspace]
members = ["crate-a", "crate-b", "my-lib"]
resolver = "2" # 依赖解析器版本
|
1
2
3
4
5
6
7
8
9
| # crate-a/Cargo.toml
[package]
name = "crate-a"
version = "0.1.0"
edition = "2021"
[dependencies]
# 可以依赖 workspace 的其他成员
my-lib = { path = "../my-lib" }
|
1
2
3
4
5
6
7
8
9
| # crate-b/Cargo.toml
[package]
name = "crate-b"
version = "0.1.0"
edition = "2021"
[dependencies]
crate-a = { path = "../crate-a" }
my-lib = { path = "../my-lib" }
|
10.2.4.2 members 成员列表
1
2
3
4
5
6
7
| [workspace]
members = [
"packages/core",
"packages/utils",
"packages/cli",
]
exclude = ["packages/deprecated"] # 排除某些包
|
10.2.4.3 Cargo.lock 在 workspace 根目录(统一版本锁定)
工作空间只有一个 Cargo.lock 文件,放在根目录,统一管理所有成员的依赖版本。
1
2
3
4
5
6
7
8
9
10
11
12
13
| my_workspace/
├── Cargo.lock # 唯一的锁文件,所有成员共享
├── Cargo.toml # workspace 配置
└── packages/
├── core/
│ ├── Cargo.toml
│ └── src/
├── utils/
│ ├── Cargo.toml
│ └── src/
└── cli/
├── Cargo.toml
└── src/
|
10.2.4.4 workspace 依赖共享(根级别 dependencies)
你可以在 workspace 根目录的 [dependencies] 中定义共享依赖,所有成员自动继承:
1
2
3
4
5
6
7
8
9
| # 根目录 Cargo.toml
[workspace]
members = ["crate-a", "crate-b"]
# workspace 级别的依赖,所有成员都可用
[dependencies]
log = "0.4"
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.0", features = ["full"] }
|
1
2
3
4
5
6
7
8
9
| # crate-a/Cargo.toml
# 不需要重复声明 log 和 serde,直接用
[package]
name = "crate-a"
version = "0.1.0"
# tokio 已经在 workspace 根级别声明了,这里可以不写
# 但如果你需要特定配置,可以覆盖:
tokio = { version = "1.0", features = ["rt-multi-thread"] }
|
10.2.5 build.rs 脚本
10.2.5.1 [build-dependencies](build.rs 的依赖)
build.rs 是一个在编译主程序之前运行的 Rust 脚本。它可以执行各种构建时任务,比如代码生成、配置检查等。
1
2
3
| [build-dependencies]
quote = "1.0"
syn = "2.0"
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| // build.rs
use std::env;
use std::fs;
use std::path::Path;
fn main() {
// 告诉 Cargo:"如果 build.rs 或 src/schema.rs 变了,就重新运行 build.rs"
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=src/schema.rs");
println!("cargo:rerun-if-env-changed=API_KEY"); // 环境变量变了也重新运行
// 生成代码
let out_dir = env::var("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("generated.rs");
let generated_code = r#"
pub fn generated_function() {
println!("这是构建时生成的代码!");
}
"#;
fs::write(&dest_path, generated_code).unwrap();
println!("cargo:rustc-cfg=generated_code"); // 设置条件编译标志
}
|
10.2.5.2 build.rs 的执行时机(编译前运行)
build.rs 在依赖解析和编译之前运行,它的输出会被 Cargo 用来配置编译过程。
graph LR
A[cargo build] --> B[运行 build.rs]
B --> C[生成代码/输出]
C --> D[编译主程序<br/>包含生成的代码]10.2.5.3 build 脚本的输出(rerun-if-changed / rustc-flags)
build.rs 通过 println! 输出特殊格式的指令给 Cargo:
| 指令 | 作用 |
|---|
cargo:rerun-if-changed=PATH | 如果 PATH 变了,重新运行 build.rs |
cargo:rerun-if-env-changed=VAR | 如果环境变量 VAR 变了,重新运行 |
cargo:rustc-cfg=FEATURE | 设置条件编译标志 |
cargo:rustc-env=VAR=VALUE | 设置环境变量,供代码使用 |
cargo:rustc-link-arg=FLAG | 传递链接器参数 |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| // build.rs 示例:生成版本信息
use std::env;
use std::fs;
fn main() {
let version = env::var("CARGO_PKG_VERSION").unwrap();
let name = env::var("CARGO_PKG_NAME").unwrap();
// 通过 rustc-env 输出,供代码使用
println!("cargo:rustc-env=BUILD_VERSION={}", version);
println!("cargo:rustc-env=BUILD_NAME={}", name);
// 生成代码
let out_dir = env::var("OUT_DIR").unwrap();
let dest = std::path::Path::new(&out_dir).join("version.rs");
let code = format!(
r#"
pub const VERSION: &str = "{}";
pub const NAME: &str = "{}";
pub const BUILD_INFO: &str = "{} (built by build.rs)";
"#,
version, name, version
);
fs::write(&dest, code).unwrap();
}
|
10.3 条件编译
10.3.1 #[cfg(…)] 属性
10.3.1.1 #[cfg(test)](仅测试编译)
#[cfg(test)] 标记的代码只在运行测试时编译:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| // src/lib.rs
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() {
assert_eq!(add(1, 2), 3);
println!("加法测试通过!"); // 加法测试通过!
}
}
|
10.3.1.2 #[cfg(feature = “…”)](特性开启时编译)
1
2
3
4
5
6
7
8
9
| #[cfg(feature = "debug")]
pub fn debug_info() {
println!("调试信息:这是 debug 版本的额外输出!"); // 调试信息:这是 debug 版本的额外输出!
}
#[cfg(not(feature = "debug"))]
pub fn debug_info() {
// 发布版本不输出调试信息
}
|
1
2
| [features]
debug = []
|
1
| cargo build --features debug # 启用 debug 特性
|
10.3.1.3 #[cfg(target_os = “linux”)](目标系统)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| #[cfg(target_os = "linux")]
fn get_home_dir() -> String {
std::env::var("HOME").unwrap()
}
#[cfg(target_os = "windows")]
fn get_home_dir() -> String {
std::env::var("USERPROFILE").unwrap()
}
#[cfg(target_os = "macos")]
fn get_home_dir() -> String {
std::env::var("HOME").unwrap()
}
fn main() {
println!("用户目录:{}", get_home_dir());
}
|
10.3.1.4 #[cfg(target_arch = “x86_64”)](目标架构)
1
2
3
4
5
6
7
8
9
| #[cfg(target_arch = "x86_64")]
fn cpu_info() {
println!("64位 x86 架构优化已启用"); // 64位 x86 架构优化已启用
}
#[cfg(target_arch = "aarch64")]
fn cpu_info() {
println!("ARM 64 位架构"); // ARM 64 位架构
}
|
10.3.1.5 #[cfg(unix)] / #[cfg(windows)](操作系统)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| #[cfg(unix)]
fn clear_screen() {
print!("{}[2J", 27 as char); // ANSI 清屏
print!("{}[H", 27 as char);
}
#[cfg(windows)]
fn clear_screen() {
std::process::Command::new("cmd")
.args(["/c", "cls"])
.spawn()
.ok();
}
fn main() {
clear_screen();
println!("跨平台清屏完成!");
}
|
10.3.2 #[cfg(feature = “…”)] Feature Gate
10.3.2.1 [features] 定义(default = [“feature-a”])
1
2
3
4
5
6
7
| [features]
default = ["basic"] # 默认启用的特性
basic = [] # 基础功能
advanced = [] # 高级功能
experimental = [] # 实验性功能
ssl = ["native-tls"] # SSL 依赖
full = ["basic", "advanced", "ssl"] # 全功能
|
10.3.2.2 optional 依赖:optional = true
1
2
3
4
| [dependencies]
# 可选依赖,默认不启用
async-runtime = { version = "1.0", optional = true }
json-support = { version = "2.0", optional = true }
|
1
2
3
| [features]
async = ["dep:async-runtime"] # 使用 dep: 前缀引用可选依赖
json = ["dep:json-support"]
|
10.3.2.3 特性组合:feature = [“dep/feature”]
1
2
3
4
5
6
7
8
9
| [dependencies]
tokio = { version = "1.0", default-features = false, features = ["sync"] }
[features]
full = [
"tokio/rt-multi-thread",
"tokio/macros",
"tokio/sync"
]
|
10.3.3 cfg_attr 与 cfg! 宏
10.3.3.1 cfg_attr(feature, attr)(特性开启时应用属性)
1
2
3
4
| #[cfg_attr(feature = "doc-tests", ignore)]
fn hidden_function() {
println!("这个函数在文档测试中被忽略"); // 这个函数在文档测试中被忽略
}
|
10.3.3.2 cfg! 宏(编译期布尔查询)
cfg! 宏在编译期求值,展开为 true 或 false 字面量(而 #[cfg] 是在编译期决定代码段是否存在):
1
2
3
4
5
6
7
8
9
10
11
12
| fn main() {
// cfg! 在编译期展开为 true 或 false 的常量,运行时直接是个常量分支
if cfg!(feature = "debug") {
println!("debug 特性已启用!"); // debug 特性已启用!
} else {
println!("这是发布版本");
}
// 查看目标信息
println!("目标 OS:{}", if cfg!(target_os = "windows") { "Windows" } else { "非 Windows" });
println!("调试构建?{}", cfg!(debug_assertions));
}
|
本章小结
这一章我们全面学习了 Rust 的模块系统。以下是关键知识点:
- Crate vs Package:Crate 是编译单元,Package 是 Cargo.toml 定义的包
- lib.rs vs main.rs:库入口 vs 二进制入口
- mod 声明:告诉 Rust"这里有个模块",编译器去找对应的 .rs 文件或 mod.rs
- 模块树:从 crate root 开始的树形结构
- use 语句:绝对路径(
crate::)、相对路径(super::、self::) - pub use:重新导出,可以简化 API
- 可见性层级:
pub > pub(crate) > pub(super) > pub(in path) > 默认 private - Cargo.toml:
[package]、[dependencies]、[features]、[profile] - 多二进制:
[[bin]] 或 src/bin/*.rs - Workspace:管理多个相关 Package
- build.rs:构建脚本,运行在编译之前
- 条件编译:
#[cfg(...)] 属性在编译期启用/禁用代码
记住:好的模块组织就像好的衣柜整理——分类清晰、容易找到、不需要翻箱倒柜。把 Rust 的模块系统玩转了,你的代码库也能从"垃圾堆"升级成"样板间"!
“在 Rust 的世界里,模块系统就是你的代码整理术。不会整理?编译器会教你做人!”