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-参数校验宏实现