macro_rules!
有了这些知识,我们终于可以引入macro_rules!
了。 如前所述,macro_rules!
本身就是一个语法扩展,也就是说它并不是Rust语法的一部分。它的形式如下:
macro_rules! $name {
$rule0 ;
$rule1 ;
// …
$ruleN ;
}
至少得有一条规则,最后一条规则后面的分号可被省略。
每条“规则”(rule
)都形如:
($pattern) => {$expansion}
实际上,分组符号可以是任意一种,选用这种(pattern
外小括号、expansion
外花括号)只是出于传统。
如果你好奇的话,macro_rules!
的调用将被展开为空。至少可以说,在AST中它被展开为空。它所影响的是编译器内部的结构,以将该宏注册进系统中去。因此,技术上讲你可以在任何一个空展开合法的位置插入macro_rules!
的调用。
匹配
当一个宏被调用时,对应的macro_rules
解释器将一一依序检查规则。对每条规则,它都将尝试将输入标记树的内容与该规则的pattern
进行匹配。某个模式必须与输入完全匹配才能被选中为匹配项。
如果输入与某个模式相匹配,则该调用项将被相应的expansion
内容所取代;否则,将尝试匹配下条规则。如果所有规则均匹配失败,则宏展开会失败并报错。
最简单的例子是空模式:
macro_rules! four {
() => {1 + 3};
}
它将且仅将匹配到空的输入(即four!()
, four![]
或four!{}
)。
注意调用所用的分组标记并不需要匹配定义时采用的分组标记。也就是说,你可以通过four![]
调用上述宏,此调用仍将被视作匹配。只有调用时的输入内容才会被纳入匹配考量范围。
模式中也可以包含字面标记树。这些标记树必须被完全匹配。将整个对应标记树在相应位置写下即可。比如,为匹配标记序列4 fn ['spang "whammo"] @_@
,我们可以使用:
macro_rules! gibberish {
(4 fn ['spang "whammo"] @_@) => {...};
}
捕获
宏模式中还可以包含捕获。这允许输入匹配在某种通用语法基础上进行,并使得结果被捕获进某个变量中。此变量可在输出中被替换使用。
捕获由$
符号紧跟一个标识符(identifier)紧跟一个冒号(:
)紧跟捕获种类组成。捕获种类须是如下之一:
item
: 条目,比如函数、结构体、模组等。block
: 区块(即由花括号包起的一些语句加上/或是一项表达式)。stmt
: 语句pat
: 模式expr
: 表达式ty
: 类型ident
: 标识符path
: 路径 (例如foo
,::std::mem::replace
,transmute::<_, int>
, …)meta
: 元条目,即被包含在#[...]
及#![...]
属性内的东西。tt
: 标记树
举例来说,下列宏将其输入捕获为一个表达式:
macro_rules! one_expression {
($e:expr) => {...};
}
Rust编译器的语法转义器将保证捕获的“准确性”。一个expr
捕获总是会捕获到一个对当前Rust版本来说完整、有效的表达式。
你可以将字面标记树与捕获混合使用,但有些限制(接下来将阐明它们)。
在扩展过程中,对于某捕获$name:kind
,我们可以通过在expansion
中写下$name
来使用它。比如:
macro_rules! times_five {
($e:expr) => {5 * $e};
}
如同宏扩展本身一样,每一处捕获也都将被替换为一个完整的AST节点。也就是说,在上例中无论$e
所捕获的是怎样的标记序列,它总会被解读成一个完整的表达式。
在一条模式中也可以出现多次捕获:
macro_rules! multiply_add {
($a:expr, $b:expr, $c:expr) => {$a * ($b + $c)};
}
重复
模式中可以包含重复。这使得匹配标记序列成为可能。重复的一般形式为$ ( ... ) sep rep
.
$
是字面标记。( ... )
代表了将要被重复匹配的模式,由小括号包围。sep
是一个可选的分隔标记。常用例子包括,
和;
。rep
是重复控制标记。当前有两种选择,分别是*
(代表接受0或多次重复)以及+
(代表1或多次重复)。目前没有办法指定“0或1”或者任何其它更加具体的重复计数或区间。
重复中可以包含任意有效模式,包括字面标记树,捕获,以及其它的重复。
在扩展部分,重复也采用相同的语法。
举例来说,下述宏将每一个element
都通过format!
转换成字符串。它将匹配0或多个由逗号分隔的表达式,并分别将它们展开成一个Vec
的push
语句。
macro_rules! vec_strs {
(
// 重复开始:
$(
// 每次重复必须有一个表达式...
$element:expr
)
// ...重复之间由“,”分隔...
,
// ...总共重复0或多次.
*
) => {
// 为了能包含多条语句,
// 我们将扩展部分包裹在花括号中...
{
let mut v = Vec::new();
// 重复开始:
$(
// 每次重复将包含如下元素,其中
// “$element”将被替换成其相应的展开...
v.push(format!("{}", $element));
)*
v
}
};
}
#
# fn main() {
# let s = vec_strs![1, "a", true, 3.14159f32];
# assert_eq!(&*s, &["1", "a", "true", "3.14159"]);
# }