09-宏中的重复
9.7 宏中的重复
除了标记树类型之外,我们还需要一种方法来重复生成代码的特定部分。标准库中的一个实际示例是vec!宏,它依赖重复呈现可变参数的假象,并允许你用以下任意一种方式创建Vec:
vec![1, 2, 3];
vec![9, 8, 7, 6, 5, 4];
让我们来看看vec!宏是如何做到这一点的。以下是标准库中vec的macro_rules!宏定义:
macro_rules! vec {
($elem:expr; $n:expr) => (
$crate::vec::from_elem($elem, $n)
);
($($x:expr),*) => (
<[_]>::into_vec(box [$($x),*])
);
($($x:expr,)*) => (vec![$($x),*])
}
我们先忽略=>右侧的细节,将重点放在左侧的最后两个匹配规则上,可以看到这些规则中新增的内容如下所示:
($($x:expr),*)
($($x:expr,)*)
重复模式的规则如下所示。
- pattern: $($var:type):注意其中的$(),为了引用它们,我们称它们为重复器。另外,让我们将内部的($x:expr)表示为X。重复器包含以下3种形式:
- *表示重复零次或多次; +表示重复至少一次或多次;
- ?表示标记最多可以重复一次。
重复器还可以包含作为重复的一部分的额外文字字符。对于vec!的情况,有一个逗号运算符,我们需要让它来区分宏调用时Vec中的每个元素。
在第1个匹配规则中,逗号在X之后。这可以支持像vec![1, 2, 3,]这样的表达式。
在第2个匹配规则中,逗号在X中元素的后面。这属于比较典型的情况,并且会匹配诸如1、2、3这样的序列。在这里我们需要两个规则,因为第一个规则不能解析没有逗号“尾随”的情况,这是比较常见的。另外,vec!宏中的模式使用了*,这表明vec!也支持调用该宏,+则不会。
现在,让我们看一下如何在代码生成块的右侧转发捕获的重复规则。在第2个匹配规则中,vec!宏只是使用相同的语法将它们转换为Box类型:
($($x:expr),*) => (<[_]>::into_vec(box [$($x),*]));
我们可以看到左侧的标记树变量声明和右侧的用法之间唯一的区别是右侧不包括标记变量的类型(expr)。第 3 个匹配规则只是捎带第 2 个匹配规则的代码生成块并调用vec![$($x),*],从而更改逗号的位置并在此调用它。这意味着我们也可以在宏中调用宏,这是一个非常强大的特性。所有的这些都可以达到相当高的元级别,这使开发者可以让宏的维护变得尽量简单。
现在,让我们看看如何构建一个使用重复特性的宏。