本章要回答的两个问题:为什么 println! 带感叹号、为什么格式串错误能编译期报错?声明宏与过程宏各自怎么生成代码?
从第 1 章的 println! 到第 4 章的 derive(Debug),宏一直在场:它接收标记流、产出代码,让"编译期写代码"成为语言能力而非外部生成器。函数接收值,宏接收语法——这一步之差使格式检查、派生实现、DSL 构造都成为可能。本章拆开这台"法条批量生成器"。
| 节 | 回答哪个问题 | 关键产出 |
|---|---|---|
| 9.1 声明宏 | 规则怎么写、怎么展开 | 自制 vec 式宏、重复模式语法 |
| 9.2 过程宏 | 派生与属性宏怎么工作 | 三类过程宏对照、派生示例 |
宏是 Rust 生态扩展性的支柱:serde、错误派生、测试框架全都建在它上面。读完再看这些库的"魔法",看到的只是规则。
| 报错/现象 | 案情 | 修法方向 |
|---|---|---|
| no rules expected | 模式没覆盖到该输入 | 补分支或调整说明符 |
| recursion limit | 递归展开过深 | 加 recursion_limit 或改迭代式 |
| macro expansion ignores token | 说明符选错 | 按语法单元类别换说明符 |
| 过程宏 panic | 生成代码不合法 | cargo expand 检查产物 |
宏的廉价性是双刃:能写不代表该写。三条经验红线——宏展开后控制流不可见(调试断点失踪);三个以上片段说明符的组合应重审是否该用函数+泛型;团队公共宏必须有文档说明展开形态。判断口诀:重复的语法结构用宏,重复的逻辑用函数。语法层面的重复(样板 impl、注册清单)才是宏的法定辖区。
宏在编译期生成代码,本章结束意味着"安全区内的全部装备"已经发齐。第 10 章走出安全区:Unsafe、FFI 与异步。前者的 raw 指针、次者的 extern 块、后者的状态机变换,处处要靠前面九章的纪律兜底——终审庭审的不只是新知识,更是旧纪律。
三个问题检验本章掌握度:给定一段宏调用,能否指出每个片段落入哪个说明符;能否解释宏卫生为什么防止了变量撞名;能否说出 derive 宏、属性宏、函数式过程宏各自的挂载点。三问之外加一条实践线:把第 8 章 worker 池的样板用 macro_rules 包一层,做完这个练习,"何时该用宏"的手感就替代了记忆。
一问:声明宏为什么不能读类型信息?答:展开发生在类型检查之前,宏只见语法树。二问:$(...),* 与 $(...),+ 的差别?答:星号允许零次,加号至少一次。三问:过程宏 crate 为什么必须独立?答:它编译成特殊动态库供编译器加载,与普通库的产物形态不同,混装无法工作。三题收口,宏章的判例就齐了。
宏章放在第九章的深意此刻可以点破:声明宏的卫生性建立在第 3 章的产权模型上(宏不偷变量,本质是标识符的属地规则);过程宏的 derive 在第 5 章、测试样板在第 7 章、worker 池在第 8 章都已预演过用途。学会宏的标志不是会写匹配规则,而是能在设计阶段识别"这段样板该宏化了"——识别力来自前八章的判例积累,这正是课程排序的用意。至此,安全区内的全部装备已经发齐,终审庭只剩三件事:越权、跨境、异步。
宏章的考核标准最终一条:在 code review 里看到一段宏,能说出它的展开形态与 hygiene 边界。达标的检验动作是 cargo expand——把自己的三个宏各展开一次,逐行读产物。读完的普遍感受是"原来这么朴素",而这种朴素正是宏设计得当的标志:好的宏展开成平凡代码,坏的宏展开成谜题。