rust过程宏
我们有这样一个结构体
1struct TestStruct{
2 a:String,
3 b:String
4}
我们希望自定义一个Display。针对1TestStruct{a:String::from("aaa"),b:String::from("bbb")}
形式化输出1a:aaa;b:bbb;
利用过程宏可以做到这一点,编译时生成1std::fmt::Display
的trait。
“ 刚开始学习过程宏,简单记录自己怎么实现的和一些坑 ”
直接上代码
1extern crate proc_macro;
2use proc_macro::TokenStream;
3
4use quote::quote;
5use syn::{parse_macro_input, Data, DeriveInput, Fields, Ident};
6
7#[proc_macro_derive(format)]
8pub fn derive_format(input: TokenStream) -> TokenStream {
9 let input = parse_macro_input!(input as DeriveInput);
10 let struct_name = input.ident;
11 let struct_str = Ident::new("struct_str", struct_name.span());
12 let expended = if let Data::Struct(r#struct) = input.data {
13 if let Fields::Named(ref fields_name) = r#struct.fields {
14 let get_selfs: Vec<_> = fields_name
15 .named
16 .iter()
17 .map(|field| {
18 let f = field.ident.as_ref().unwrap();
19
20 quote! {
21 stringify!(#f),&self.#f
22 }
23 })
24 .collect();
25
26 let format_string = "{}:{};".repeat(get_selfs.len());
27 let format_literal = proc_macro2::Literal::string(format_string.as_str());
28 let struct_fields = quote! {
29 #(#get_selfs),*
30 };
31
32 quote! {
33 impl std::fmt::Display for #struct_name{
34 fn fmt(&self,f:&mut std::fmt::Formatter)->std::fmt::Result{
35 write!(f , #format_literal , #struct_fields)
36 }
37 }
38 }
39 } else {
40 panic!("sorry, may it's a complicated struct.")
41 }
42 } else {
43 panic!("sorry, Show is not implemented for union or enum type.")
44 };
45 expended.into()
46}
难点有两个:
-
1proc_macro2::Literal
1format!("{}",str);
语句中格式控制字符串1"{}"
是一个字面量1literal
。quote内部使用的第三方库
1proc_macro2
,quote语法1#ident
只接受1proc_macro2
的1{TokenStream,Ident,....}
。在上边代码中,我们需要将一个String变量转为一个字面量。这个一定要用
1proc_macro2::Literal
,用默认库1proc_macro::Literal
,会报错mismatches types。要了命了才看出来这么个事 -
1#(#get_selfs),*
quote的语法糖1write!(f , #format_literal , #struct_fields)
写出1write!(f , "{:?}" , (#(#get_selfs),*))
这样可以过编译,最后这个参数会生成一个元组,这又没法自定义格式了。 写成1write!(f , #format_literal , #(#get_selfs),*)
又过不了编译。 所以用1let struct_fields = quote!{#(#get_selfs),*}
套了一层。