rust - macro_rules! 過程宏學習筆記 macro_rules!

rust中宏大致分兩種:

  • 過程宏: 形如 println!(), vec!() 這類
  • 屬性宏: 形如 #[derive(Debug)] 這種, 寫在struct頭上的
    其中過程宏定義起來比較簡單, 使用方便,簡潔

0x01 解讀過程宏的定義

macro_rules! cc {
    () => {1+3};
}

如上所示, 這是一個比較簡單的宏, 名稱叫cc, 使用方法: cc!().
然後就會在編譯出結果:4.
看一下上述代碼結構, () => {1+3}; 其實對應的是一個模式匹配.本例中表示 無參數下, 會匹配到 1+3

再來一個例子:

macro_rules! times3 {
    ($e:expr) => {$e * 3};
    ($a:expr, $b:expr, $c:expr)=> {$a * ($b + $c)};
}

這個例子有兩個模式匹配, 第一個包含一個參數, 第二個模式包含三個參數, 理解起來也很簡單.
需要說明的是 參數的類型, 大致分以下幾種, 上面使用比較常見的類型 expr: 即表達式

 item :例如 函數、結構、模塊等等
 block : 代碼塊(例如 表達式或者複製代碼塊,用花括號包起來)
 stmt:賦值語句(statement)
 pat :Pattern ,匹配
 expr :表達式,expression  : 1 + 2
 ty:類型
 ident:標記,識別碼
 path:路徑(例如:foo, ::std::men::replace,transmute::<_,int>,...)
 meta:元項目;在 #[...] 和 #![...] 屬性裏面的內容
 tt:單 token tree : 1, 2

0x02 多參數匹配

類似於java中的 arg..., 過程宏定義中, 也有相應的寫法, 來看個例子:

macro_rules! rep {
    () => {-1};
    ($ ($e:expr) ,+) => {
        {
            let mut v = Vec::new();
            $(
                v.push($e);
            )+
            v
        }
    };
    ($ ($e:expr) +) => {
        {
            let mut sum = 0;
            $(
                sum = sum + $e;
            )+
            sum
        }
    };
}

/// 使用例子:
    println!("rep no param {}", rep!());
    println!("rep has params {:?}", rep![1, 2, 3]);
    println!("rep sum params {:?}", rep!(1 2 3 4 5));

本例包含三種參數方式:

  • 無參, 上面已經解釋過了.
  • 以逗號分隔的參數串
  • 以 空格分隔的參數串
    需要注意的是, 參數匹配, 和 值的使用是一致的, 都採用 $( ... )+ 寫法進行套用即可.

0x03 關於類型 tt

macro_rules! param_count {
    ($a:tt + $b:tt) => {"got an  a+b expression"};
    ($i:ident)=>{"got an identifier"};
    ($a:tt kiss $b:tt) => {$a + $b};
    ($($e:tt)*)=>{"got some tokens"};
}

    println!("param_cnt 1: {}", param_count!(3+4));
    println!("param_cnt 2: {}", param_count!(a));
    println!("param_cnt 3: {}", param_count!(4 5 6));
    println!("param_cnt 4: {}", param_count!(7 kiss 8));

--- 結果
param_cnt 1: got an  a+b expression
param_cnt 2: got an identifier
param_cnt 3: got some tokens
param_cnt 4: 15

可以看到, 在宏的參數裏, 可以寫非關鍵字的任意字符, 這個可用於自定義 DSL

0x04 綜合使用: 自定義 struct 模板

macro_rules! my_cc {
    (
        struct $name:ident {
             $(pub $field_name:ident: $field_type:ty,)*
        }
    )
 => {
        struct $name {
            $($field_name: $field_type,)*
        }

        impl $name {

            fn log(self) {
                $( println!("{} -> {:?}", stringify!($field_name), self.$field_name); )*
            }
        }
    }
}

my_cc!(struct Hello {
    pub name:String,
    pub size:String,
});

let t = Hello { name: String::from("gorey"), size: String::from("18") };
t.log();

---- 結果

name -> "gorey"
size -> "18"

通過 my_cc!的方法來定義一個struct, 同時自動實現了可以打印參數名/值的日誌方法.
後續, 可以擴展成用於值校驗.

寫在最後, 本篇的擴展實用文: rust-參數校驗宏實現

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