12.2 过程宏
Rust 的过程宏是一种更强大、更灵活的元编程工具。与基于模式匹配的 macro_rules! 不同,过程宏以 Rust 函数的形式编写,接收输入的代码(以 token 流表示),经过任意逻辑处理后,返回新的 Rust 代码。它们在编译期运行,并能操作完整的抽象语法树(AST),因此适用于需要复杂分析或生成的场景。
过程宏必须定义在独立的 proc-macro crate 中(Cargo.toml 设置 proc-macro = true),且只能导出宏,不能导出普通函数或类型。
Rust 支持三类过程宏:
派生宏(Derive Macros)
派生宏通过 #[derive(...)] 为结构体、枚举等类型自动实现 trait。标准库中的 #[derive(Debug)]、#[derive(Clone)] 就是典型例子。自定义派生宏允许你为自己的 trait 自动生成实现。
使用形式:
#[derive(MyTrait)]
struct MyStruct;
在过程宏 crate 中,通过 #[proc_macro_derive(MyTrait)] 定义对应函数。
属性宏(Attribute Macros)
属性宏允许你定义新的 类属性(类似 #[test] 或 #[tokio::main]),可应用于函数、结构体、模块等项。它们能完全替换或包装原始项。
使用形式:
#[my_attribute]
fn my_function() {
// ...
}
定义时使用 #[proc_macro_attribute],函数接收属性名和被修饰项的 token 流,返回修改后的代码。
例如,#[tokio::main] 实际上是一个属性宏,它将 async fn main() 重写为启动 tokio 运行时的同步函数。
函数式宏(Function-like Macros)
函数式宏在语法上类似函数调用,但由过程宏实现:
my_macro!(arg1, arg2);
与 macro_rules! 宏不同,它能执行任意 Rust 代码来生成输出,支持更复杂的逻辑(如读取文件、解析 DSL 等)。通过 #[proc_macro] 定义。
基本结构示例
以下是一个简单的派生宏骨架(需在独立的 proc-macro crate 中):
// lib.rs in proc-macro crate
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
let expanded = quote! {
impl #name {
pub fn hello_macro() {
println!("Hello, Macro! My name is {}!", stringify!(#name));
}
}
};
TokenStream::from(expanded)
}
配合主 crate 使用:
// main.rs
use my_proc_macro::HelloMacro;
#[derive(HelloMacro)]
struct Pancakes;
fn main() {
Pancakes::hello_macro(); // 输出: Hello, Macro! My name is Pancakes!
}
此例依赖 syn(用于解析 AST)和 quote(用于生成代码),这是编写过程宏的事实标准组合。
优势与代价
- 优势:功能强大,可实现任意代码生成;能访问完整类型结构;错误信息可定制。
- 代价:编译速度略慢;调试较复杂;需额外依赖(如
syn、quote);必须放在独立 crate。
小结
过程宏将 Rust 的元编程能力提升到新高度,使开发者能构建出高度抽象且零成本的 API。无论是自动实现序列化(如 serde)、生成测试桩,还是嵌入领域特定语言,过程宏都是关键工具。理解其三种形式及基本工作流程,是掌握现代 Rust 高级开发的重要一步。后续章节将进一步展示如何编写实用的派生宏。