模块、属性与宏
📚 Rust 课程系列
当代码量增长,模块系统负责拆分与组织代码,属性与条件编译让编译器按需生成代码,宏则提供元编程能力。本章把这三块放在一起,它们共同决定了 Rust 项目的结构与编译期行为。
模块系统
| 概念 | 语法 | 说明 |
|---|---|---|
| 定义模块 | mod name { ... } | 内联或指向文件 |
| 公开可见 | pub fn/struct/const | 默认私有,pub 才对外可见 |
| 引入路径 | use path::to::item | 绝对路径 crate::,相对路径 self::/super:: |
| 重命名 | use X as Y | 解决同名冲突 |
| 通配符 | use path::* | ⚠️ 仅建议在测试或 prelude 中使用 |
💡 提示:Rust 的模块默认私有——与 Java/C++ 的类成员默认包级可见不同,Rust 要求显式
pub才能跨模块访问。这一设计迫使 API 边界在编译期就被明确标注。
🔄 对比:Rust 模块 ≈ Java 的包 + 访问修饰符,但 Rust 没有
protected,只有pub/pub(crate)/pub(super)/pub(in path)四级。
文件系统与模块
mod network; 会让编译器寻找 network.rs 或 network/mod.rs。一个 crate 的入口是 src/main.rs(二进制)或 src/lib.rs(库)。
1 | |
💡 提示:在库 crate 中把公共 API 暴露在
lib.rs顶层,内部实现放子模块并只pub出必要接口——这是 Rust 的"门面模式"惯例。
可见性细粒度控制
1 | |
⚠️ 注意:
pub(crate)是实际项目中最常用的可见性——它隐藏内部实现同时允许 crate 内各模块共享,比全pub更安全。
路径系统速查
1 | |
⚠️ 注意:避免模块间循环依赖——通过拆分模块、提取共享 trait 或把共享接口移到顶层模块来解决。
属性与条件编译
属性以 #[...] 形式给编译器提供元数据,是 Rust 编译期行为控制的核心机制。
常用属性速查
| 属性 | 用途 | 示例 |
|---|---|---|
#[derive(Trait)] | 自动生成 trait 实现 | #[derive(Debug, Clone)] |
#[inline] / #[inline(always)] / #[inline(never)] | 内联建议 | 性能关键路径用 always |
#[cfg(condition)] | 条件编译 | #[cfg(target_os = "linux")] |
#[cfg(test)] | 仅测试时编译 | 测试辅助代码隔离 |
#[allow(lint)] | 抑制警告 | #[allow(dead_code)] |
#[deprecated] | 标记废弃 | #[deprecated(since="1.0", note="用 new_fn")] |
#[cfg_attr(cfg, attr)] | 条件应用属性 | #[cfg_attr(feature="nightly", feature(...))] |
条件编译
1 | |
在 Cargo.toml 中通过 [features] 定义可选特性,实现"按需编译":
1 | |
💡 提示:feature 的核心价值是减少默认依赖体积。
serde、tracing等库都通过 feature 实现零成本抽象——不启用则不编译、不链接。
🔄 对比:Rust feature ≈ Go build tags,但 Rust feature 在
Cargo.toml声明且可组合,Go build tags 靠文件头注释。
内联属性(进阶)
1 | |
🔬 进阶:
#[inline]对当前 crate 的函数几乎无意义(编译器可见函数体时已能自主内联决策),它主要影响跨 crate 调用——告诉下游编译器"请考虑内联此函数"。#[inline(always)]应仅在性能剖析确认必要时使用。
常用宏
| 宏 | 用途 | 备注 |
|---|---|---|
println! / format! | 格式化输出/构造字符串 | print! 无换行 |
vec! | 创建 Vec | vec![1, 2, 3] |
panic! | 不可恢复错误 | 详见第 7 章 |
todo! / unimplemented! | 标记未实现 | todo! 语义更清晰 |
unreachable! | 逻辑上不可达 | 到达则 panic |
dbg! | 调试打印表达式+位置 | 比 println! 更方便排查 |
assert! / assert_eq! / assert_ne! | 运行时断言 | debug_assert_* 仅 debug 模式 |
1 | |
💡 提示:
dbg!会捕获表达式值、文件名和行号,且返回表达式的值(不中断代码流),比println!更适合临时调试。发布前记得删除。
⚠️ 注意:宏能生成重复代码,但会增加调试难度。如果函数或 trait 已能满足需求,优先使用函数/trait——宏是最后手段。
宏系统
声明宏(macro_rules!)
基于模式匹配生成代码,是最常用的自定义宏形式:
1 | |
片段类型与重复符:
| 片段 | 匹配 | 重复符 | 含义 |
|---|---|---|---|
expr | 表达式 | * | 0 次或多次 |
ident | 标识符 | + | 1 次或多次 |
ty | 类型 | ? | 0 次或 1 次 |
path | 路径 | ||
stmt | 语句 | ||
block | 块表达式 | ||
tt | 单个 token 树 | 最灵活,过程宏常用 |
1 | |
过程宏(Procedural Macro)
过程宏接收并操作 token 流,能力更强但必须放在独立的 proc-macro = true crate 中。三种形式:
| 类型 | 形式 | 典型用例 |
|---|---|---|
| derive | #[derive(Trait)] | 自动生成 Debug/Clone/Serialize 实现 |
| attribute | #[attr] | #[tokio::main]、#[serde(rename_all)] |
| function-like | name!() | vec、sql!() |
1 | |
🔬 进阶:过程宏在独立的编译单元中运行,无法直接引用项目中的类型——它只操作 token 流。这意味着过程宏不能做类型检查,只能做语法级变换。编写过程宏时务必提供清晰的
compile_error!信息,否则用户会看到难以理解的展开错误。
宏的卫生性(Hygiene)
Rust 宏是卫生的——不会意外捕获外部作用域的标识符:
1 | |
🔄 对比:C/C++ 预处理器宏是不卫生的——
#define可以意外捕获或污染任何标识符。Rust 的卫生性在编译期就杜绝了这类问题。
调试宏
cargo expand(需安装 cargo-expand)可以查看宏展开后的真实代码,是排查宏问题最重要的工具。
1 | |
⚠️ 注意:宏展开后的代码量可能远超预期,
cargo expand输出可能很长。建议配合less或重定向到文件查看。


