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-參數校驗宏實現