
rust macro! libs
Rust 宏开发常用库介绍
Rust 的宏分为两种:
- 声明宏 (Declarative Macros):使用
macro_rules!
定义,类似于模式匹配和替换,语法相对固定。 - 过程宏 (Procedural Macros):更强大和灵活,可以接收 Rust 代码作为输入 Token Stream,执行任意 Rust 代码进行分析和转换,然后生成新的 Token Stream 作为输出。过程宏又分为三种:自定义派生宏 (
#[derive(...)]
)、属性宏 (#[attribute(...)]
) 和函数式宏 (macro_name!(...)
)。
在开发过程宏时,通常会用到以下几个核心库:
1. proc_macro (Rust 内置库)
用途
这是 Rust 编译器提供的一个内置库,包含了定义过程宏所需的基础类型和功能。它提供了表示 Rust 代码 Token Stream 的 TokenStream
类型,以及用于处理 token 的基本 API,如 Ident
(标识符)、Punct
(标点)、Literal
(字面量) 等。它也提供了 Span
信息,用于将宏生成的代码与原始源代码位置关联,以便编译器报告错误。
使用
在定义过程宏的 crate 的 Cargo.toml
中,需要将 crate 类型设置为 proc-macro
:
[lib]
proc-macro = true
然后在代码中直接 extern crate proc_macro;
引入即可。过程宏函数的签名通常是 (TokenStream) -> TokenStream
或 (TokenStream, TokenStream) -> TokenStream
(对于属性宏)。
// src/lib.rs in a proc-macro crate
extern crate proc_macro;
use proc_macro::TokenStream;
#[proc_macro_derive(MyDerive)]
pub fn my_derive(input: TokenStream) -> TokenStream {
// input 是需要应用派生宏的 struct/enum/union 的 TokenStream
println!("Input: {}", input); // 打印输入 Token Stream (用于调试)
// 在这里进行 TokenStream 的处理和生成新的 TokenStream
let output = TokenStream::new(); // 示例:生成空的 TokenStream
output // 返回生成的 TokenStream
}
注意
proc_macro
中的类型是编译器特定的,不能在非过程宏 crate 中直接使用或测试。
2. proc-macro2
用途
proc-macro2
是 proc_macro
库的一个“克隆”或替代实现,它提供了与 proc_macro
几乎相同的 API,但其类型可以在任何 crate 中使用,而不仅仅是过程宏 crate。它的主要目的是:
- 允许在非过程宏 crate 中构建和操作 Token Stream:这使得宏辅助库 (helper libraries) 能够独立于过程宏进行开发和测试。
- 使过程宏可单元测试:由于
proc_macro
类型不能在普通测试函数中使用,proc-macro2
允许你在单元测试中模拟输入 Token Stream,并检查宏生成的输出 Token Stream。
使用
在 Cargo.toml
中添加依赖:
[dependencies]
proc-macro2 = "1.0" # 使用最新版本
在过程宏代码中,通常会先将输入的 proc_macro::TokenStream
转换为 proc_macro2::TokenStream
进行处理:
// src/lib.rs in a proc-macro crate
extern crate proc_macro;
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2; // 别名以区分
#[proc_macro_derive(MyDerive)]
pub fn my_derive(input: TokenStream) -> TokenStream {
let input2: TokenStream2 = input.into(); // 转换为 proc-macro2::TokenStream
// 使用 proc-macro2::TokenStream 进行处理...
let output2 = TokenStream2::new(); // 示例
output2.into() // 转换回 proc_macro::TokenStream 返回
}
在单元测试中:
#[cfg(test)]
mod tests {
use super::*;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote; // 通常与 quote 库一起使用
#[test]
fn test_my_derive() {
// 模拟输入 Token Stream
let input: TokenStream2 = quote! {
struct MyStruct {
field1: i32,
field2: String,
}
};
// 调用宏函数 (需要将输入/输出转换为 proc_macro::TokenStream)
let input_pm = input.into();
let output_pm = my_derive(input_pm);
let output2: TokenStream2 = output_pm.into();
// 检查生成的 output2 是否符合预期
// 可以将其转换为字符串进行比较,或者使用 syn 解析后比较 AST
let expected_output: TokenStream2 = quote! {
// 预期生成的代码
};
assert_eq!(output2.to_string(), expected_output.to_string());
}
}
3. syn
用途
syn
是一个强大的 Rust 源代码解析库。过程宏接收的输入是 Token Stream,而直接操作 Token Stream 是非常繁琐的。syn
可以将输入的 Token Stream 解析成结构化的抽象语法树 (AST),让你能够以更方便的方式访问和操作代码的各个部分,如 struct
、enum
、函数、表达式、类型等。它提供了丰富的类型来表示 Rust 语法结构的各个节点。
使用
在 Cargo.toml
中添加依赖:
[dependencies]
syn = { version = "2.0", features = ["full", "derive"] } # 使用最新版本,并启用常用 feature
full
feature 启用解析所有 Rust 语法结构的功能。derive
feature 启用解析派生宏输入所需的功能(如DeriveInput
)。
在过程宏代码中,使用 syn::parse_macro_input!
宏来解析输入的 Token Stream:
extern crate proc_macro;
use proc_macro::TokenStream;
use syn::{parse_macro_input, DeriveInput};
use quote::quote; // 通常与 quote 库一起使用
#[proc_macro_derive(MyDerive)]
pub fn my_derive(input: TokenStream) -> TokenStream {
// 将输入 TokenStream 解析为 DeriveInput 结构体
let input_ast = parse_macro_input!(input as DeriveInput);
// 现在可以通过 input_ast 访问 struct/enum 的名称、字段、泛型等信息
let name = &input_ast.ident;
let generics = &input_ast.generics;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
// 根据 input_ast 的信息生成新的 Token Stream (通常使用 quote 库)
let expanded = quote! {
// 示例:生成一个简单的 impl 块
impl #impl_generics MyTrait for #name #ty_generics #where_clause {
// ... 实现 MyTrait 的方法 ...
}
};
expanded.into() // 转换回 proc_macro::TokenStream 返回
}
syn
提供了大量的类型(如 ItemStruct
, ItemEnum
, FnArg
, Type
, Expr
等)来表示 Rust 代码的各个部分,以及用于遍历和修改 AST 的 Trait(如 Visit
, VisitMut
, Fold
)。
4. quote
用途
quote
库提供了一个 quote!
宏,用于方便地构建要生成的 Rust 代码的 Token Stream。与手动创建 proc_macro2::TokenStream
中的 Ident
, Punct
, Literal
等 token 相比,quote!
宏允许你以类似编写实际 Rust 代码的方式来构建 Token Stream,并支持通过 #var
语法方便地插入 syn
解析出的变量或其他 Token Stream 片段。
使用
在 Cargo.toml
中添加依赖:
[dependencies]
quote = "1.0" # 使用最新版本
在过程宏代码中,结合 syn
解析出的 AST 来使用 quote!
宏生成代码:
extern crate proc_macro;
use proc_macro::TokenStream;
use syn::{parse_macro_input, DeriveInput};
use quote::quote;
#[proc_macro_derive(MyDerive)]
pub fn my_derive(input: TokenStream) -> TokenStream {
let input_ast = parse_macro_input!(input as DeriveInput);
let name = &input_ast.ident;
let generics = &input_ast.generics;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
// 使用 quote! 宏构建要生成的代码
let expanded = quote! {
impl #impl_generics MyTrait for #name #ty_generics #where_clause {
fn my_method(&self) {
println!("Hello from {}!", stringify!(#name)); // stringify! 宏将 token 转换为字符串字面量
}
}
};
expanded.into() // quote! 宏返回的是 proc_macro2::TokenStream,需要转换为 proc_macro::TokenStream 返回
}
quote!
宏支持 #var
插入变量,#( ... )*
进行重复,这使得根据输入的结构生成重复的代码变得非常方便(例如,为 struct 的每个字段生成代码)。
其他常用辅助库:
- darling:专门用于简化派生宏中处理属性(Attributes)的库。它可以方便地从
#[derive(...)]
或字段、枚举变体上的属性中提取信息并映射到 Rust 结构体中,减少手动解析属性的工作量。 - strum / strum_macros:提供了一系列用于处理枚举(Enum)的派生宏,例如自动生成
FromStr
、Display
、EnumIter
等 Trait 的实现。如果你需要为枚举实现这些常见功能,直接使用strum
提供的派生宏通常比自己编写过程宏更简单。 - proc-macro-error / proc-macro-error2:用于在过程宏中报告更友好的编译错误,而不是直接
panic
。它们允许你在宏代码中指出错误发生在用户代码的哪个具体位置(通过Span
信息)。 - synstructure:另一个用于简化派生宏开发的库,提供了一些方便的 Trait 和宏来处理结构体和枚举的结构。
总结
在 Rust 过程宏开发中,proc_macro
是基础,proc-macro2
使得测试和构建更灵活,syn
负责解析输入的代码结构,而 quote
负责方便地构建输出的代码结构。这四个库是过程宏开发的核心工具。像 darling
和 strum
这样的库则是在特定场景下(如处理属性或枚举)提供便利的辅助工具。理解并熟练使用 syn
和 quote
是进行复杂过程宏开发的关键。