blog.dopana

Back

Rust 中级系列第 16 篇暨终篇。在 Rust 中,“宏(Macro)”指的是一组强大的元编程(Metaprogramming)机制 —— 即编写能够在编译期自动生成代码的代码。

与运行时接收值的普通函数不同,宏操作的是源码语法标记(Tokens),在编译器进行类型检查和生成机器码前完成展开。

graph TD
    A["Rust 宏系统"] --> B["声明式宏 (Declarative)<br/>macro_rules!"]
    A --> C["过程宏 (Procedural)<br/>操作 TokenStream"]
    C --> D["自定义 Derive<br/>#[derive(CustomTrait)]"]
    C --> E["类属性宏 (Attribute-like)<br/>#[route(GET, '/')]"]
    C --> F["类函数宏 (Function-like)<br/>sql!(...)"]

浅显易懂(ELI5):3D 代码打印机#

理解 函数(Function) 与 宏(Macro) 的差异:

  • 函数:就像榨汁机,你在营业期间(运行时 / Runtime)投入橙子,它产出一杯橙汁。
  • 宏:就像 3D 打印机,在工厂正式开门前(编译期 / Compile-time),直接为你打印并组装出一台专属的榨汁机!

1. 声明式宏:macro_rules!#

声明式宏是 Rust 中最常见的宏,使用类似于 match 模式匹配的语法对代码 Token 进行匹配与替换:

以简化的 vec! 宏为例:

语法解析:#

  • $x:expr:匹配任意有效的 Rust 表达式并捕获到变量 $x 中。
  • $( ... ),*:匹配用逗号 , 分隔的 0 次或多次(*)重复模式。

2. 过程宏(Procedural Macros)#

如果说声明式宏是模版匹配替换,那么过程宏更像是运行在编译期的 Rust 函数:接收 Token 流(TokenStream)作为输入,经过任意 Rust 逻辑处理后,输出全新的 TokenStream

过程宏分为 3 类:

A. 自定义 Derive 宏#

通过 #[derive(MyTrait)] 自动生成特征的实现代码:

// 宏定义(位于独立的 proc-macro crate 中)
#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
    // 解析 AST 语法树并生成 impl HelloMacro for StructName 代码
}

// 使用方式
#[derive(HelloMacro)]
struct Pancakes;
rust

B. 类属性宏(Attribute-like Macros)#

允许创建自定义属性挂载在结构体、函数或模块上:

#[route(GET, "/users")]
fn get_users() {
    // Web 路由定义
}
rust

C. 类函数宏(Function-like Macros)#

形式类似于函数调用,但可以解析领域特定语言(DSL):

let sql = sql!(SELECT * FROM users WHERE id = 1);
rust

函数 vs 宏 对比#

比较维度函数 (Functions)宏 (Macros)
执行阶段运行时 (Runtime)编译期 (Compile-time)
参数支持固定类型与参数数量变长参数(可接收任意数量参数)
操作对象运行时具体数值源码语法结构 (AST / Tokens)
可维护性逻辑清晰,易于阅读调试语法相对复杂,调试难度较高

总结#

  • 宏赋予了 Rust 零运行时开销的强大元编程能力。
  • macro_rules! 为绝大多数模板型代码生成提供了简单易用的声明式方案。
  • 过程宏通过自定义 Derive、类属性宏和类函数宏,让开发者可以直接操纵 Rust 的抽象语法树。

参考资料#