「深挖Rust」Rust macro 实例 - 6

362 阅读2分钟

「这是我参与2022首次更文挑战的第 22 天,活动详情查看:2022首次更文挑战」。


原文:blog.logrocket.com/macros-in-r…

现在我们要在 input_fn 上做 DFS,并插入打印语句。syn 提供了一个 Fold trait,可以在任何item上实现 DFS。我们只需要修改与我们要操作的token类型相对应的trait方法即可。

impl Fold for Args {
    fn fold_expr(&mut self, e: Expr) -> Expr {
        match e {
            // for changing assignment like a=5
            Expr::Assign(e) => {
                // check should print
                if self.should_print_expr(&e.left) {
                    self.assign_and_print(*e.left, &e.eq_token, *e.right)
                } else {
                    // continue with default travesal using default methods
                    Expr::Assign(fold::fold_expr_assign(self, e))
                }
            }
            // for changing assigment and operation like a+=1
            Expr::AssignOp(e) => {
                // check should print
                if self.should_print_expr(&e.left) {
                    self.assign_and_print(*e.left, &e.op, *e.right)
                } else {
                    // continue with default behaviour
                    Expr::AssignOp(fold::fold_expr_assign_op(self, e))
                }
            }
            // continue with default behaviour for rest of expressions
            _ => fold::fold_expr(self, e),
        }
    }

    // for let statements like let d=9
    fn fold_stmt(&mut self, s: Stmt) -> Stmt {
        match s {
            Stmt::Local(s) => {
                if s.init.is_some() && self.should_print_pat(&s.pat) {
                    self.let_and_print(s)
                } else {
                    Stmt::Local(fold::fold_local(self, s))
                }
            }
            _ => fold::fold_stmt(self, s),
        }
    }
}

Fold trait是用来对item进行 DFS 操作的。它能够让你对不同的token类型使用不同的行为。

现在我们可以使用 fold_item_fn 在我们解析的代码中注入 print

#[proc_macro_attribute]
pub fn trace_var(args: TokenStream, input: TokenStream) -> TokenStream {
    // parse the input
    let input = parse_macro_input!(input as ItemFn);
    // parse the arguments
    let mut args = parse_macro_input!(args as Args);
    // create the ouput
    let output = args.fold_item_fn(input);
    // return the TokenStream
    TokenStream::from(quote!(#output))
}

这个代码例子来自 syn examples repo,它是学习过程宏的绝佳资源。

派生宏

Rust中的自定义派生宏允许自动实现 Trait。这些宏可以让你使用 #[derive(Trait)] 来实现Traitsyn 对派生宏有很好的支持。

#[derive(Trait)]
struct MyStruct{}

要在Rust中编写一个自定义派生宏,我们可以使用 DeriveInput 来解析派生宏的输入,然后我们使用 proc_macro_derive! 来定义一个自定义的派生宏。

#[proc_macro_derive(Trait)]
pub fn derive_trait(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let input = parse_macro_input!(input as DeriveInput);

    let name = input.ident;

    let expanded = quote! {
        impl Trait for #name {
            fn print(&self) -> usize {
                println!("{}","hello from #name")
           }
        }
    };

    proc_macro::TokenStream::from(expanded)
}

更高级的过程性宏请看 syn repo 中的这个例子。

函数宏

其实函数宏与声明宏类似,它们是用宏调用操作符 ! 来调用的,看起来像函数调用。它们对括号内的代码进行操作。

下面是如何在Rust中写一个函数宏:

#[proc_macro]
pub fn a_proc_macro(_input: TokenStream) -> TokenStream {
    TokenStream::from(quote!(
        fn anwser() -> i32 {
            5
        }
    ))
}

函数宏不是在运行时执行,而是在编译时执行。它们可以在Rust代码中的任何地方使用。函数宏也接受一个 TokenStream,并返回一个 TokenStream

说完上述3中的子类,总结一下使用过程宏的优点包括:

  • 使用 span 更好地处理错误
  • 对输出有更好的控制
  • 来自社区强大的crate解析能力: syn & quote
  • 比声明宏更强大

总结

本Rust宏教程中,我们介绍了Rust中宏的基础知识:包括声明宏和过程宏,并介绍了如何使用各种语法和社区生态的crate来编写这两种类型的宏。我们还概述了使用每种类型的Rust宏的优势。