Rust - 過程宏

一、添加過程宏依賴庫

1、過程宏,類似其他語言的運行時反射機制
2、官方的過程宏庫爲proc_macro,不推薦直接使用
3、推薦更友好的synquoteproc_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的結構體如下(重要的是pathtokens,這也是主要解析的部分):
    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()的詞條

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章