Appearance
9.4 常用的宏开发库
在前面的例子中使用了proc_macro
,但是对于proc_macro
有如下缺点:
- 需要在
proc-macro
类型的 crate 中才能使用,必须位于它们自己的 library crate 中。 - 不太好进行测试
proc-macro2
解决了这些问题,api 也基本兼容。
proc-macro2
、quote
和 syn
目前是 Rust 生态系统中最受欢迎的三个宏工具库。接下来我会分别介绍一下这三个库,并提供相应的文档链接和示例代码。
proc-macro2:这个库提供了一组工具来处理
proc_macro::TokenStream
类型,方便高效地实现自定义操作。具体而言,这个库可以让您更好地解析、操作和生成属性宏和过程宏。示例代码:
rustuse proc_macro2::{Span, Ident}; use quote::quote; #[proc_macro] pub fn my_macro(input: TokenStream) -> TokenStream { let ident = Ident::new("foo", Span::call_site()); quote! { struct #ident { field: i32, } impl #ident { pub fn new() -> Self { Self { field: 0 } } } } }
syn:一个强大的解析库,能将原始的标记流转换成结构化的 Rust 代码表示(如 struct 的定义、字段等),让您轻松地分析、修改和生成 Rust 代码。 与
proc-macro2
和quote
配合使用,可以编写更加复杂且功能强大的属性宏和过程宏。示例代码:
rust// 引入必要的库 use proc_macro::TokenStream; use quote::quote; use syn; // 定义一个名为 HelloMacro 的派生宏 // `#[proc_macro_derive(HelloMacro)]` 是宏的入口点 #[proc_macro_derive(HelloMacro)] pub fn hello_macro_derive(input: TokenStream) -> TokenStream { // 使用 syn 将输入的 TokenStream 解析成一个 AST let ast = syn::parse(input).unwrap(); // 实现宏的逻辑 impl_hello_macro(&ast) } // 这是一个辅助函数,用于生成具体的实现代码 fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream { // `ast.ident` 能拿到结构体的名称 (Identifier) let name = &ast.ident; // 使用 quote! 宏来生成新的代码 let gen = quote! { // #name 会被替换成上面捕获的结构体名称 impl HelloMacro for #name { fn hello_macro() { // stringify! 会将标识符转换为字符串 println!("Hello, Macro! My name is {}!", stringify!(#name)); } } }; // 将生成的代码转换回 TokenStream 并返回 gen.into() }
代码解析:
#[proc_macro_derive(HelloMacro)]
:这个属性声明了一个名为HelloMacro
的派生宏,并指定hello_macro_derive
函数是它的实现。syn::parse(input
):这行代码是魔法发生的地方。syn 将编译器传入的、代表struct Pancakes { ... }
的原始TokenStream
解析成一个结构化的syn::DeriveInput
对象。&ast.ident
:从解析出的 DeriveInput 中,我们可以轻松地获取结构体的名称(标识符)。quote! { ... }
:这是代码生成的核心。它创建了一个新的TokenStream
。在quote!
内部,#variable
语法用于将变量的值插入到生成的代码中。stringify!(#name)
:这个内置宏会将 Pancakes 这个标识符直接转换为字符串 "Pancakes"。
quote:它提供了一个
quote!
宏,可以让我们像编写模板字符串一样轻松地生成新的TokenStream
,避免手动构造样板代码的麻烦。示例代码:
rustuse quote::quote; fn main() { let my_ident = quote! { x }; let my_value = quote! { 42 }; let tokens = quote! { let #my_ident = #my_value; }; println!("{}", tokens); }
综上,这三个宏工具库是 Rust 生态系统中最常用的库之一,非常适合编写属性宏和过程宏。您可以根据自己的需求选择使用它们中的一个或多个,并结合实际需求进行开发和编写代码。
通过这三个工具的结合:定义的过程宏首先被解析为proc_macro::TokenStream
,接着再被解析为proc_macro2::TokenStream
, 再使用 syn 库解析为 AST (这里的不同于编译生成的 AST ),最后再通过 quote 解析为TokenStream
,最终会被扩展到编译过程TokenStream
中,进而再被编译 AST 。
更多的学习可以到:David Tolnay的“Rust Latam:过程宏研讨会”