过程宏是一种扩展Rust编译器和提供可用于扩展该语言的插件的方法。过程宏的工作原理非常简单:取一段称为输入TokenStream的代码,将其转换为抽象语法树(ast),从输入处获得的内容构建一个新的TokenStream(使用syn::parse()方法),并将其作为输出代码注入编译器。
最近在项目中频繁看到各种使用,例如:zombodb中如下使用:
#[pg_extern(immutable, parallel_safe)]
在我们平时debug时:
#[derive(Debug)]
那么我们如何实现类似Debug的功能呢?
使用侧:
#[derive(StructShow)]
pub struct Point {
x: f64,
y: f64
}
我们需要使用proc-macro来实现该功能。
cargo new --lib my-macro
在Cargo.toml中设置:
[lib]
proc-macro = true
[dependencies]
syn = { version = "1.0.82", features = ["full", "extra-traits"] }
quote = "1.0.10"
一个最基本的例子为:
use proc_macro::TokenStream;
use syn::parse;
#[proc_macro_derive(StructShow)]
pub fn whatever_you_want(tokens: TokenStream) -> TokenStream {
let ast: syn::DeriveInput = syn::parse(tokens).unwrap();
TokenStream::new()
}
在这个例子中使用了proc_macro,但是对于proc_macro有如下缺点:
因此proc-macro2解决了这些问题,api也基本兼容。
#[proc_macro_derive(StructShow)]
pub fn show(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let output = transform_stream(proc_macro2::TokenStream::from(input));
proc_macro::TokenStream::from(output)
}
在这里将核心操作转入transform_stream函数,在该函数中使用的是proc_macro2,因此可以方便测试在其他crate中直接使用。
随后,内部实现debug功能。
功能非常简单,要实现debug功能,只需要实现Debug trait即可,因此使用quote!将rust语法转位TokenStream返回给编译器即可。
fn transform_stream(input: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
let ast: ItemStruct = syn::parse2(input).unwrap();
let struct_type = ast.ident;
let implemented_show = quote! {
// 下面就是Display trait的定义了
// use std::fmt; // 不要这样import,因为std::fmt是全局的,无法做到卫生性(hygiene)
// 编译器会报错重复import fmt当你多次使用Show之后
impl std::fmt::Display for #struct_type {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{} x: {}, y: {}", stringify!(#struct_type), self.x, self.y)
}
}
};
implemented_show.into()
}
这样,我们在进行打印输出的时候便可以debug了。
let p= Point{x: 1.1, y: 2.3};
println!("{}", p);
今天写的这个例子比较简单,过程宏功能很强大,后续继续研究,期待一起探讨~
本节完~