一、添加過程宏依賴庫
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<Attribute>, // 屬性,類似#[sqlx(A)]
pub vis: Visibility, // 可見性
pub ident: Ident, // 名稱
pub generics: Generics, // 泛型
pub data: Data, // 類型數據,結構體、枚舉體、聯合體
}
示例結構體
struct MyStruct<T>
where T: Sized {
a: String,
b: Vec<u8>,
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()
的詞條