关于rust宏补充(二)-派生过程宏示例

rust中过程宏示例:

准备工作

[lib]
proc-macro = true

[dependencies]
syn = { version = "1.0", features = ["full"] }
quote = "1.0"
proc-macro2 = "1.0"

定义一个过程宏: proc_macro_derive

extern crate proc_macro;

use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{self, parse_macro_input, Data, DataStruct, DeriveInput, Fields, FieldsNamed, Type};

#[proc_macro_derive(Builder, attributes(builder))]
pub fn derive(input: TokenStream) -> TokenStream {
    eprintln!("===input: {:#?}", input);
    // Parse the input tokens into a syntax tree.
    let input = parse_macro_input!(input as DeriveInput);
    // eprintln!("{:?}", format!("===ast: {}", input.into()));
    eprintln!("===ast input atrrs: {:#?}", input.attrs);
    eprintln!("===ast input name: {:#?}", input.ident);
    eprintln!("===ast input vis: {:#?}", input.vis);
    eprintln!("===ast input generics: {:#?}", input.generics);
    eprintln!("===ast input data: {:#?}", input.data);
    // Used in the quasi-quotation below as `#name`.
    let name = input.ident;

    // 类似:CommonBuilder格式
    let builder = format_ident!("{}Builder", name);

    /// 获取struct/enum/union的内容部分
    /// 类似struct的如下内容:DataStruct.data部分
    /// {
    ///     executable: String,
    ///     args: Vec<String>,
    ///     env: Vec<String>,
    ///     current_dir: String,
    /// }
    /// 经过使用syn库转换为syn.AST 大概格式如下:
    /// Struct(
    ///     DataStruct {
    ///         struct_token: Struct,           # 标识当前是一个struct
    ///         fields: Named(                  # 在当前struct中定义的field
    ///             FieldsNamed {               # 字段都是其命名的
    ///                 brace_token: Brace,
    ///                 named: [                # 命名field
    ///                     Field {             # 类似: executable: String,
    ///                         attrs: [],      # 该field在定义时指定了attributes
    ///                         vis: Inherited, # 该field的可见性
    ///                         ident: Some(    # 该field的名称及代码中位置
    ///                             Ident {
    ///                                 ident: "executable",  # 名称
    ///                                 span: #0 bytes(1016..1026), # 位置
    ///                             },
    ///                         ),
    ///                         colon_token: Some( # 冒号:
    ///                             Colon,
    ///                         ),
    ///                         ty: Path(           # 该field类型
    ///                             TypePath {      # 类型路径: 类似A::B::C
    ///                                 qself: None,
    ///                                 path: Path {
    ///                                     leading_colon: None,
    ///                                     segments: [
    ///                                         PathSegment {
    ///                                             ident: Ident {
    ///                                                 ident: "String",
    ///                                                 span: #0 bytes(1028..1034),
    ///                                             },
    ///                                             arguments: None,
    ///                                         },
    ///                                     ],
    ///                                 },
    ///                             },
    ///                         ),
    ///                     },
    ///                     Comma,
    ///                 ],
    ///             },
    ///         ),
    ///         semi_token: None,
    ///     },
    /// )

    // 获取当前struct中所有fields: DataStruct::fields::FieldsNamed部分
    let data: FieldsNamed = match input.data {
        Data::Struct(DataStruct {
            fields: Fields::Named(n),
            ..
        }) => n,
        other => unimplemented!("{:?}", other),
    };

    // 遍历每个命名的field:名称、类型; 构建(名称, 类型Option<真实类型>)
    let fields = data.named.iter().filter_map(|field| {
        let ty = &field.ty;
        match &field.ident {
            Some(ident) => Some((ident, ty, inner_for_option(ty))),
            _ => None,
        }
    });

    // 遍历每个field:名称、类型; 构建(名称, Option<真实类型>)
    let names = data.named.iter().filter_map(|field| match &field.ident {
        None => None,
        Some(ident) => Some((ident, inner_for_option(&field.ty))),
    });

    // 构建每个字段的初始化值:None; 类似 字段name: None
    let initialize = names.clone().map(|(name, _)| quote! { #name: None });

    //
    let extract = names.clone().map(|(name, option)| match option {
        None => quote! { #name: self.#name.clone()? },
        Some(_) => quote! { #name: self.#name.clone() },
    });

    // 构建所有字段的模版: 类似 字段name: 类型Option<#ty>
    let quoted_fields = fields.clone().map(|(name, ty, option)| match option {
        None => quote! { #name: Option<#ty> },
        Some(ty) => quote! { #name: Option<#ty> },
    });

    // 构建每个字段对应的setter方法
    // 格式类似:
    // pub fn 字段名称(&mut self, 值value: 类型#ty) {
    //  self.#name = Some(value);
    //  self
    // }
    let methods = fields.clone().map(|(name, ty, option)| match option {
        None => quote! {
            pub fn #name(&mut self, value: #ty) -> &mut Self {
                self.#name = Some(value);
                self
            }
        },

        Some(ty) => quote! {
            pub fn #name(&mut self, value: #ty) -> &mut Self {
                self.#name = Some(value);
                self
            }
        },
    });

    // 构建最终的Builder模式的对应的模版
    let expanded = quote! {
        // 生成Builder,并初始化struct不同字段的内容
        impl #name {
            fn builder() -> #builder {
                #builder {
                    #(
                        #initialize,
                    )*
                }
            }
        }

        // 定义Builder
        struct #builder {
            #(
                #quoted_fields,
            )*
        }

        // 实现Builder的build及不同字段赋值的方法
        impl #builder {
            pub fn build(&self) -> Option<#name> {
                Some(#name {
                    #(
                        #extract,
                    )*
                })
            }

            #(
                #methods
            )*
        }
    };
    eprintln!("===expanded: {:#?}", expanded);

    // 最终输出proc_macro::TokenStream,并入被编译器rustc输出AST
    TokenStream::from(expanded)
}

fn inner_for_option(ty: &Type) -> Option<Type> {
    match ty {
        Type::Path(syn::TypePath {
            path: syn::Path { segments, .. },
            ..
        }) if segments[0].ident == "Option" => {
            let segment = &segments[0];

            match &segment.arguments {
                syn::PathArguments::AngleBracketed(generic) => {
                    match generic.args.first().unwrap() {
                        syn::GenericArgument::Type(ty) => Some(ty.clone()),
                        _ => None,
                    }
                }
                _ => None,
            }
        }

        _ => None,
    }
}

测试

use derive_builder::Builder;

#[derive(Builder)]
pub struct Command {
    executable: std::string::String,
    args: Vec<String>,
    env: Vec<String>,
    current_dir: String,
}

fn main() {}
use derive_builder::Builder;

#[derive(Builder)]
pub struct Command {
    executable: String,
    args: Vec<String>,
    env: Vec<String>,
    current_dir: String,
}

fn main() {
    let builder = Command::builder();

    let _ = builder;
}

更多例子

补充

派生式过程宏解析
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343