Skip to content

9.4 常用的宏开发库

在前面的例子中使用了proc_macro,但是对于proc_macro有如下缺点:

  1. 需要在proc-macro类型的 crate 中才能使用,必须位于它们自己的 library crate 中。
  2. 不太好进行测试

proc-macro2 解决了这些问题,api 也基本兼容。

proc-macro2quotesyn 目前是 Rust 生态系统中最受欢迎的三个宏工具库。接下来我会分别介绍一下这三个库,并提供相应的文档链接和示例代码。

  1. proc-macro2:这个库提供了一组工具来处理 proc_macro::TokenStream 类型,方便高效地实现自定义操作。具体而言,这个库可以让您更好地解析、操作和生成属性宏和过程宏。

    示例代码:

    rust
    use 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 }
                }
            }
        }
    }
  2. syn:一个强大的解析库,能将原始的标记流转换成结构化的 Rust 代码表示(如 struct 的定义、字段等),让您轻松地分析、修改和生成 Rust 代码。 与 proc-macro2quote 配合使用,可以编写更加复杂且功能强大的属性宏和过程宏。

    示例代码:

    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()
    }

    代码解析:

    1. #[proc_macro_derive(HelloMacro)]:这个属性声明了一个名为 HelloMacro 的派生宏,并指定 hello_macro_derive 函数是它的实现。

    2. syn::parse(input):这行代码是魔法发生的地方。syn 将编译器传入的、代表 struct Pancakes { ... } 的原始 TokenStream 解析成一个结构化的 syn::DeriveInput 对象。

    3. &ast.ident:从解析出的 DeriveInput 中,我们可以轻松地获取结构体的名称(标识符)。

    4. quote! { ... }:这是代码生成的核心。它创建了一个新的 TokenStream。在 quote! 内部,#variable 语法用于将变量的值插入到生成的代码中。

    5. stringify!(#name):这个内置宏会将 Pancakes 这个标识符直接转换为字符串 "Pancakes"。

  3. quote:它提供了一个 quote! 宏,可以让我们像编写模板字符串一样轻松地生成新的 TokenStream,避免手动构造样板代码的麻烦。

    示例代码:

    rust
    use 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:过程宏研讨会

Rust的宏(二)

Released under the MIT License