📚 Rust 课程系列

  1. 课程概览
  2. 基础语法
  3. 函数与输入输出
  4. 所有权、借用与生命周期
  5. 结构体、枚举与模式匹配
  6. 类型系统:泛型、trait 与多态
  7. 集合与容器
  8. 错误处理与 Panic 恢复
  9. 模块、属性与宏(本文)
  10. 智能指针、迭代器与闭包
  11. 并发与异步编程
  12. Unsafe Rust 与常用 trait 详解
  13. 工具链、Cargo 与外部 crate
  14. 最佳实践、性能与调试

当代码量增长,模块系统负责拆分与组织代码,属性与条件编译让编译器按需生成代码,宏则提供元编程能力。本章把这三块放在一起,它们共同决定了 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.rsnetwork/mod.rs。一个 crate 的入口是 src/main.rs(二进制)或 src/lib.rs(库)。

1
2
3
// lib.rs — 库 crate 入口
mod front_of_house; // 加载 front_of_house.rs 或 front_of_house/mod.rs
pub use crate::front_of_house::hosting; // re-export,简化外部路径

💡 提示:在库 crate 中把公共 API 暴露在 lib.rs 顶层,内部实现放子模块并只 pub 出必要接口——这是 Rust 的"门面模式"惯例。

可见性细粒度控制

1
2
3
pub(crate) fn internal_helper() {}   // 仅 crate 内可见,最常用
pub(super) fn sibling_only() {} // 仅父模块可见
pub(in crate::domain) fn domain_fn() {} // 指定路径范围内可见

⚠️ 注意pub(crate) 是实际项目中最常用的可见性——它隐藏内部实现同时允许 crate 内各模块共享,比全 pub 更安全。

路径系统速查

1
2
3
4
crate::front_of_house::hosting::add_to_waitlist();  // 绝对路径
front_of_house::hosting::add_to_waitlist(); // 相对路径
super::some_function(); // 访问父模块
use std::collections::HashMap as Map; // 重命名

⚠️ 注意:避免模块间循环依赖——通过拆分模块、提取共享 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
2
3
4
5
#[cfg(target_os = "linux")]
fn platform_init() { /* Linux specific */ }

#[cfg(feature = "serde")]
impl serde::Serialize for Config { /* ... */ }

Cargo.toml 中通过 [features] 定义可选特性,实现"按需编译":

1
2
3
[features]
default = ["serde"]
serde = ["dep:serde"]

💡 提示:feature 的核心价值是减少默认依赖体积。serdetracing 等库都通过 feature 实现零成本抽象——不启用则不编译、不链接。

🔄 对比:Rust feature ≈ Go build tags,但 Rust feature 在 Cargo.toml 声明且可组合,Go build tags 靠文件头注释。

内联属性(进阶)

1
2
3
#[inline]          // 建议内联(编译器自行决定)
#[inline(always)] // 强制内联(除极少数情况外)
#[inline(never)] // 禁止内联

🔬 进阶#[inline] 对当前 crate 的函数几乎无意义(编译器可见函数体时已能自主内联决策),它主要影响跨 crate 调用——告诉下游编译器"请考虑内联此函数"。#[inline(always)] 应仅在性能剖析确认必要时使用。

常用宏

用途备注
println! / format!格式化输出/构造字符串print! 无换行
vec!创建 Vecvec![1, 2, 3]
panic!不可恢复错误详见第 7 章
todo! / unimplemented!标记未实现todo! 语义更清晰
unreachable!逻辑上不可达到达则 panic
dbg!调试打印表达式+位置println! 更方便排查
assert! / assert_eq! / assert_ne!运行时断言debug_assert_* 仅 debug 模式
1
2
3
4
let v = vec![1, 2, 3];
dbg!(&v); // [src/main.rs:2] &v = [1, 2, 3]
println!("{:?}", v); // 调试格式
println!("0x{:x}", 255); // ff — 十六进制

💡 提示dbg! 会捕获表达式值、文件名和行号,且返回表达式的值(不中断代码流),比 println! 更适合临时调试。发布前记得删除。

⚠️ 注意:宏能生成重复代码,但会增加调试难度。如果函数或 trait 已能满足需求,优先使用函数/trait——宏是最后手段。

宏系统

声明宏(macro_rules!)

基于模式匹配生成代码,是最常用的自定义宏形式:

1
2
3
4
5
6
macro_rules! say_hello {
() => { println!("Hello!"); };
($name:expr) => { println!("Hello, {}!", $name); };
}
say_hello!(); // Hello!
say_hello!("Rust"); // Hello, Rust!

片段类型与重复符:

片段匹配重复符含义
expr表达式*0 次或多次
ident标识符+1 次或多次
ty类型?0 次或 1 次
path路径
stmt语句
block块表达式
tt单个 token 树最灵活,过程宏常用
1
2
3
4
macro_rules! sum {
($($x:expr),+) => { $($x)++ }; // 重复匹配,+ 分隔
}
let total = sum!(1, 2, 3); // 6

过程宏(Procedural Macro)

过程宏接收并操作 token 流,能力更强但必须放在独立的 proc-macro = true crate 中。三种形式:

类型形式典型用例
derive#[derive(Trait)]自动生成 Debug/Clone/Serialize 实现
attribute#[attr]#[tokio::main]#[serde(rename_all)]
function-likename!()vec![](实际是声明宏)、sql!()
1
2
3
4
5
#[derive(Debug, Clone, PartialEq)]   // derive 宏
struct Point { x: i32, y: i32 }

#[tokio::main] // attribute 宏
async fn main() {}

🔬 进阶:过程宏在独立的编译单元中运行,无法直接引用项目中的类型——它只操作 token 流。这意味着过程宏不能做类型检查,只能做语法级变换。编写过程宏时务必提供清晰的 compile_error! 信息,否则用户会看到难以理解的展开错误。

宏的卫生性(Hygiene)

Rust 宏是卫生的——不会意外捕获外部作用域的标识符:

1
2
3
4
5
macro_rules! make_function {
() => { fn helper() -> i32 { 42 } };
}
make_function!();
// helper(); // 错误:helper 不在作用域中

🔄 对比:C/C++ 预处理器宏是不卫生的——#define 可以意外捕获或污染任何标识符。Rust 的卫生性在编译期就杜绝了这类问题。

调试宏

cargo expand(需安装 cargo-expand)可以查看宏展开后的真实代码,是排查宏问题最重要的工具。

1
2
cargo expand    # 展开当前 crate 所有宏
cargo expand specific_module # 仅展开指定模块

⚠️ 注意:宏展开后的代码量可能远超预期,cargo expand 输出可能很长。建议配合 less 或重定向到文件查看。