macro_rules
macro_rules是rust中定义宏的主要手段,例如 assert_eq!
macro_rules! assert_eq {($left:expr, $right:expr) => ({match (&$left, &$right) {(left_val, right_val) => {if !(*left_val == *right_val) {panic!("assertion failed");}}}});}
注意,这里assert_eq后是没有 ! 的, ! 只有在调用宏的时候才需要。
macro的函数体就是一系列的规则,类似match 例如
(pattern1) => (template1);(pattern2) => (template2);
在调用宏的时候可以使用 {} [] () , 这些都是等价的,我们通常在assert_eq后用(), vec!后用[], macro_rules!后用{}, 这些都是些约定俗成的东西
assert_eq!(gcd(6,10), 2);assert_eq![gcd(6,10), 2];assert_eq!{gcd(6,10), 2};
宏展开
当assert_eq! 调用时,rust首先模式匹配参数例如
rust的模式匹配类似正则匹配,与正则不同的是,rust的模式匹配作用于 tokens,这个例子中, $left:expr 告诉rust匹配一个表达式,并赋值$left, 然后匹配 , , 最后匹配第二个表达式,并赋值$right
先来看下这个宏,为什么这个宏要创建 left_val和right_val这两个变量?为什么不能直接写成 if $left != $right ? 加入当我们调用 assert_eq!(letters.pop, Some('z')) 的时候,所有的$left, $right都会被直接替换成这两个表达式,这里letters.pop会被多次调用,每次调用都会产生副作用,导致结果不正确,所以我们需要$left $right仅执行一次。
Repetition
再看看vec! 这个宏
let buffer = vec![0_u8; 1000];let numbers = vec!["udon", "ramen", "soba"];
这个宏有3条rule
macro_rules! vec{($elem:expr ; &n:expr) => {::std::vec::from_elem($elem, $n)};( $( $x:expr ),* ) => {<[_]>::into_vec(Box::new([ $( $x ),* ]))};( $( $x:expr ),+ ,) => {vec![ $( $x ),* ]};}
vec!["udon", "ramen", "soba"]; 匹配的是第二条, $( $x:expr ),* 这个表示匹配表达式 $x:expr, 0或多次,以逗号为分割符,更通用些表示为 $( PATTERN ),* , 这里*也可以替换为 +,表示至少一次,或者?表示0或1次,这和正则表达式一样

这里 $x 不仅仅表示一个表达式,而代表的是一个list。
再来看下 <[_]>::into_vec , 这个告诉rust自己去推导元素类型,接着 Box::new([ $( $x ),* ] 和上面匹配一样,将匹配到的元素以逗号为分隔符塞入模板中。我们也可以这样写
($( $x:expr ),*) => {{let mut v = Vec::new();$( v.push($x); )*v}}
再看第三条规则,rust不会自动处理 trailing commas, 所以标准的做法是添加一条额外的规则来匹配最后一个逗号, 当匹配到额外的逗号后,递归调用自己,并将最后一个逗号抹去。
( $( $x:expr ),+ ,) => { // if trailing comma is present,vec![ $( $x ),* ] // retry without it};
这里我们可以看到在rust中,宏是可以递归调用的
