Rust 過程宏之derive派生宏入門

需求:

獲取枚舉類型附加信息,如備注說明抬闯,傳統(tǒng)作法如下:

實現(xiàn) comment 方法根據(jù)匹配值返回對應字符串

enum Robot {
      Alex,
      Bob
}

impl Robot {
      fn comment(&self) -> &'static str {
            match self {
                  Robot::Alex => "這是Alex",
                  Robot::Bob => "這是Bob",
            }
      }
}

// test
println!("alex comment: {}", Robot::Alex.comment()); // alex comment: 這是Alex

傳統(tǒng)作法有一個問題,如果有很多枚舉关筒,就需要各自實現(xiàn)對應的 comment 方法實現(xiàn)画髓,很麻煩,是否可以簡化呢平委?

可以的!

其中 comment 方法的實現(xiàn)就屬于模板代碼夺谁。這種重復性的工作就可以考慮使用宏廉赔,最終簡化如下:

除了 comment 也可以附加 其它信息如 姓名、年齡等

#[derive(HelloMacro)]
enum Robot {
    #[oic_column(name = "alex_name", age = 22, comment = "這是Alex")]
    Alex,
    #[oic_column(name = "bob_name", age = 50, comment = "這是Bob")]
    Bob,
}

// 測試可以獲取一樣的結(jié)果
println!("alex comment: {}", Robot::Alex.comment()); // alex comment: 這是Alex

什么是過程宏

過程宏中文文檔參考: https://zjp-cn.github.io/rust-note/proc/quote.html

過程宏(Procedure Macro) 是Rust中的一種特殊形式的宏匾鸥,提供比普通宏更強大的功能蜡塌。過程宏主要分三類:

  • 派生宏(Derive macro):用于結(jié)構(gòu)體(struct)、枚舉(enum)勿负、聯(lián)合(union)類型馏艾,可為其實現(xiàn)函數(shù)或特征(Trait)。
  • 屬性宏(Attribute macro):用在結(jié)構(gòu)體奴愉、字段琅摩、函數(shù)等地方,為其指定屬性等功能锭硼。如標準庫中的#[inline]房资、#[derive(...)]等都是屬性宏。
  • 函數(shù)式宏(Function-like macro):用法與普通的規(guī)則宏類似檀头,但功能更加強大轰异,可實現(xiàn)任意語法樹層面的轉(zhuǎn)換功能。

1.派生宏示例:

#[proc_macro_derive(Builder)]
fn derive_builder(input: TokenStream) -> TokenStream {
    let _ = input;

    unimplemented!()
}

其使用方法如下:

#[derive(Builder)]
struct Command {
    // ...
}

2.屬性宏示例

#[proc_macro_attribute]
fn sorted(args: TokenStream, input: TokenStream) -> TokenStream {
    let _ = args;
    let _ = input;

    unimplemented!()
}

其使用方法如下:

#[sorted]
enum Letter {
    A,
    B,
    C,
    // ...
}

3.函數(shù)式宏示例

#[proc_macro]
pub fn seq(input: TokenStream) -> TokenStream {
    let _ = input;

    unimplemented!()
}

其使用方法如下:

seq! { n in 0..10 {
    /* ... */
}}

功能實現(xiàn)

本例主要使用派生宏實現(xiàn)

項目創(chuàng)建可以參數(shù)示例倉庫https://github.com/imbolc/rust-derive-macro-guide

第一步 創(chuàng)建測試項目

測試項目: my-macro-test
宏項目: my-derives

項目結(jié)構(gòu)最終如下:

my-macro-test/
      my-derives/             // 子項目
            src/
                  attributes.rs
                  lib.rs        
            Cargo.toml
      src/
            main.rs             // 主項目入口
      Cargo.toml

文件 my-macro-test/Cargo.toml

# my-macro-test/Cargo.toml
[package]
name = "my-macro-test"
version = "0.1.0"
edition = "2021"

[dependencies]
# 根據(jù)路徑指定子項目
my_derives = { path = "my-derives" }

文件 my-macro-test/my-derives/Cargo.toml

[package]
# my-macro-test/my-derives/Cargo.toml
name = "my-derives"
version = "0.1.0"
edition = "2021"

[lib]
proc-macro = true

[dependencies]
syn = { version = "^1", features = ["full"] }
quote = "^1"
proc-macro2 = "^1"
bae = "^0"

重要的第三方庫:

  • quote 把 Rust 語法樹的數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)化為源代碼的標記 (tokens)
  • syn 主要是一個解析庫暑始,用于把 Rust 標記流解析為 Rust 源代碼的語法樹
  • bae 簡化屬性數(shù)據(jù)的處理

第二步 自定義屬性定義

文件: my-derives/attributes.rs

// my-derives/attributes.rs
use bae::FromAttributes;
use syn;

#[derive(Default, FromAttributes, Debug)]
pub struct OicColumn {
    pub name: Option<syn::Lit>,
    pub age: Option<syn::Lit>,
    pub comment: Option<syn::Lit>,
}

第三步 HelloMacro 派生宏實現(xiàn)

文件: my-derives/lib.rs

mod attributes;

use proc_macro::{self, TokenStream};
use quote::{quote};
use syn::{parse_macro_input, DeriveInput};
use attributes::{OicColumn};

// HelloMacro 定義
#[proc_macro_derive(HelloMacro, attributes(oic_column))]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
    let input: DeriveInput = parse_macro_input!(input);
    // ident 當前枚舉名稱
    let DeriveInput { ident, .. } = input;

    let mut comment_arms = Vec::new();

    if let syn::Data::Enum(syn::DataEnum { variants, .. }) = input.data {
        for variant in variants {
            // 當前枚舉項名稱如 Alex, Box
            let ident_item = &variant.ident;
            // 根據(jù)屬性值轉(zhuǎn)為 OicColumn 定義的結(jié)構(gòu)化數(shù)據(jù)
            if let Ok(column) = OicColumn::from_attributes(&variant.attrs) {
                // 獲取屬性中的comment信息
                let msg: &syn::Lit = &column.comment.unwrap();

                // 生成 match 匹配項 Robot::Alex => "msg"
                comment_arms.push(quote! ( #ident::#ident_item => #msg ));
            } else {
                comment_arms.push(quote! ( #ident::#ident_item => "" ));
            }
        }
    }

    if comment_arms.is_empty() {
        comment_arms.push(quote! ( _ => "" ));
    }

    // 實現(xiàn) comment 方法
    let output = quote! {
        impl #ident {
            fn comment(&self) -> &'static str {
                match self {
                    #(#comment_arms),*
                }
            }
        }
    };
    output.into()
}

#(#comment_arms),* 為數(shù)據(jù)解構(gòu)語法搭独,具體參考:https://docs.rs/quote/1.0.21/quote/macro.quote.html

第四步 測試代碼實現(xiàn)

文件: my-macro-test/main.rs

use my_derives::HelloMacro;

#[derive(HelloMacro)]
enum Robot {
    #[oic_column(name = "alex_name", age = 22, comment = "這是Alex")]
    Alex,
    #[oic_column(name = "bob_name", age = 50, comment = "這是Bob")]
    Bob,
    Apple,
}

fn main() {
    // test comment: Alex: "這是Alex", ----- Bob: "這是Bob"
    println!("test comment: Alex: {:?}, ----- Bob: {:?}", Robot::Alex.comment(), Robot::Bob.comment());
    // test comment apple: ""
    println!("test comment apple: {:?}", Robot::Apple.comment());
}

cargo run 運行測試輸出結(jié)果如注釋部分。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末廊镜,一起剝皮案震驚了整個濱河市牙肝,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖惊奇,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件互躬,死亡現(xiàn)場離奇詭異,居然都是意外死亡颂郎,警方通過查閱死者的電腦和手機吼渡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來乓序,“玉大人寺酪,你說我怎么就攤上這事√媾” “怎么了寄雀?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長陨献。 經(jīng)常有香客問我盒犹,道長,這世上最難降的妖魔是什么眨业? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任急膀,我火速辦了婚禮,結(jié)果婚禮上龄捡,老公的妹妹穿的比我還像新娘卓嫂。我一直安慰自己,他們只是感情好聘殖,可當我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布晨雳。 她就那樣靜靜地躺著,像睡著了一般奸腺。 火紅的嫁衣襯著肌膚如雪餐禁。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天洋机,我揣著相機與錄音坠宴,去河邊找鬼。 笑死绷旗,一個胖子當著我的面吹牛喜鼓,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播衔肢,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼庄岖,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了角骤?” 一聲冷哼從身側(cè)響起隅忿,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤心剥,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后背桐,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體优烧,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年链峭,在試婚紗的時候發(fā)現(xiàn)自己被綠了畦娄。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡弊仪,死狀恐怖熙卡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情励饵,我是刑警寧澤驳癌,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站役听,受9級特大地震影響颓鲜,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜典予,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一灾杰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧熙参,春花似錦、人聲如沸麦备。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽凛篙。三九已至黍匾,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間呛梆,已是汗流浹背锐涯。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留填物,地道東北人纹腌。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像滞磺,于是被迫代替她去往敵國和親升薯。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內(nèi)容