「这是我参与2022首次更文挑战的第 22 天,活动详情查看:2022首次更文挑战」。
现在我们要在 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)]
来实现Trait
。syn
对派生宏有很好的支持。
#[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宏的优势。