1、过程宏,类似其他语言的运行时反射机制
2、官方的过程宏库为proc_macro
,不推荐直接使用
3、推荐更友好的syn
、quote
和proc_macro2
这3个库
4、过程宏,必须写在单独的lib类型的crate中
[lib]
proc-macro = true
[dependencies]
syn = "1.0.17"
quote = "1.0.3"
proc-macro2 = "1.0.10"
导入库
extern crate proc_macro;
use syn::{DeriveInput, parse_macro_input};
use quote::*;
use proc_macro::TokenStream;
syn
解析derive
过程宏的词条流,生成DeriveInput
结构体1、DeriveInput
结构体,字段如下
pub struct DeriveInput {
pub attrs: Vec, // 属性,类似#[sqlx(A)]
pub vis: Visibility, // 可见性
pub ident: Ident, // 名称
pub generics: Generics, // 泛型
pub data: Data, // 类型数据,结构体、枚举体、联合体
}
示例结构体
struct MyStruct
where T: Sized {
a: String,
b: Vec,
c: T,
}
2、使用宏parse_macro_input!
解析输入的词条流TokenStream
,并打印结构体名称
#[proc_macro_derive(MyTest)]
pub fn derive_my_test(input: TokenStream) -> TokenStream {
let derive_input = parse_macro_input!(input as DeriveInput);
println!("struct name is : {}", derive_input.ident.to_token_stream());
// ...
}
3、解析泛型相关的几个词条,并打印
let (impl_generics, ty_generics, where_clause) = derive_input.generics.split_for_impl();
println!("impl_generics: {}", impl_generics.to_token_stream()); // < T >
println!("ty_generics: {}", ty_generics.to_token_stream()); // < T >
println!("where_clause: {}", where_clause.to_token_stream()); // where T : Sized
4、解析结构体字段,可能是有名字段
,也可能是无名字段
,并打印
match derive_input.data {
syn::Data::Struct(ref data_struct) => match data_struct.fields {
// field: (0) a: String
// field: (1) b: Vec < u8 >
// field: (2) c: T
syn::Fields::Named(ref fields_named) => {
for (index, field) in fields_named.named.iter().enumerate() {
println!("named struct field: ({}) {}: {}", index, field.ident.to_token_stream(), field.ty.to_token_stream())
}
},
// field: (0) : String
// field: (1) : Vec < u8 >
// field: (2) : T
syn::Fields::Unnamed(ref fields_unnamed) => {
for (index, field) in fields_unnamed.unnamed.iter().enumerate() {
println!("unnamed struct field: ({}): {}", index, field.ty.to_token_stream())
}
},
syn::Fields::Unit => {
println!("unit struct field: None")
},
},
_ => (),
}
5、给derive
添加属性(attributes
),打印DeriveInput
中的attrs
Attribute
的结构体如下(重要的是path
和tokens
,这也是主要解析的部分): pub struct Attribute {
pub pound_token: Token![#],
pub style: AttrStyle,
pub bracket_token: token::Bracket,
pub path: Path,
pub tokens: TokenStream,
}
// 实现方
#[proc_macro_derive(ShowStruct, attributes(OptionDesc))]
// 使用方
#[derive(ShowStruct)]
#[OptionDesc(ctx = "This is a description")]
// 打印具体的解析信息
let _attr: Vec<()> = derive_input.attrs.iter().map(|x| {
// 打印结果: path: OptionDesc, tokens: (ctx = "This is a description")
println!("path: {}, tokens: {}", x.path.to_token_stream(), x.tokens);
}).collect();
6、查看结构体的可见性
,并打印
match derive_input.vis {
syn::Visibility::Public(ref vp) => {
println!("pub struct : {}", vp.to_token_stream()); // pub
},
syn::Visibility::Crate(ref vc) => {
println!("crate struct : {}", vc.to_token_stream()); // crate
},
syn::Visibility::Restricted(ref vr) => {
println!("pub (crate | super | in some::module) struct : {}", vr.to_token_stream()); // pub (xxx)
},
_ => {
println!("struct : {}", "inherited"); // inherited
},
}
7、创建新的ident
,比如函数名称
let struct_name = derive_input.ident;
let fn_name = syn::Ident::new("show_struct", proc_macro2::Span::call_site());
quote!
宏,生成proc_macro2
的词条流1、示例代码
let proc_macro2_token_stream = quote! (
impl #impl_generics #struct_name #ty_generics #where_clause {
fn #fn_name(&self) {
println!("Todo: ...")
}
}
);
TokenStream::from(proc_macro2_token_stream)
// proc_macro2_token_stream.into()
2、使用#xxx
捕获可以to_token_stream()
的词条