1. 1. Introduction
  2. 2. 宏,彻底剖析
    1. 2.1. 语法扩展
      1. 2.1.1. 源码解析过程
      2. 2.1.2. AST中的宏
      3. 2.1.3. 展开
    2. 2.2. macro_rules!
    3. 2.3. 细枝末节
      1. 2.3.1. 再探捕获与展开
      2. 2.3.2. 卫生性
      3. 2.3.3. 不是标识符的标识符
      4. 2.3.4. 调试
      5. 2.3.5. 作用域
      6. 2.3.6. 导入/导出
  3. 3. 宏,实践介绍
  4. 4. 常用模式
    1. 4.1. 回调
    2. 4.2. 标记树撕咬机
    3. 4.3. 内用规则
    4. 4.4. 下推累积
    5. 4.5. 重复替代
    6. 4.6. 尾部分隔符
    7. 4.7. 标记树聚束
    8. 4.8. 可见性
    9. 4.9. 临时措施
  5. 5. 轮子
    1. 5.1. AST强转
    2. 5.2. 计数
    3. 5.3. 枚举解析
  6. 6. 实例注解
    1. 6.1. Ook!

macro_rules!

有了这些知识,我们终于可以引入macro_rules! 了。 如前所述,macro_rules!本身就是一个语法扩展,也就是说它并不是Rust语法的一部分。它的形式如下:

macro_rules! $name {
    $rule0 ;
    $rule1 ;
    // …
    $ruleN ;
}Run

至少得有一条规则,最后一条规则后面的分号可被省略。

每条“规则”(rule)都形如:

    ($pattern) => {$expansion}Run

实际上,分组符号可以是任意一种,选用这种(pattern外小括号、expansion外花括号)只是出于传统。

如果你好奇的话,macro_rules!的调用将被展开为空。至少可以说,在AST中它被展开为空。它所影响的是编译器内部的结构,以将该宏注册进系统中去。因此,技术上讲你可以在任何一个空展开合法的位置插入macro_rules!的调用。

匹配

当一个宏被调用时,对应的macro_rules解释器将一一依序检查规则。对每条规则,它都将尝试将输入标记树的内容与该规则的pattern进行匹配。某个模式必须与输入完全匹配才能被选中为匹配项。

如果输入与某个模式相匹配,则该调用项将被相应的expansion内容所取代;否则,将尝试匹配下条规则。如果所有规则均匹配失败,则宏展开会失败并报错。

最简单的例子是空模式:

macro_rules! four {
    () => {1 + 3};
}Run

它将且仅将匹配到空的输入(即four!()four![]four!{})。

注意调用所用的分组标记并不需要匹配定义时采用的分组标记。也就是说,你可以通过four![]调用上述宏,此调用仍将被视作匹配。只有调用时的输入内容才会被纳入匹配考量范围。

模式中也可以包含字面标记树。这些标记树必须被完全匹配。将整个对应标记树在相应位置写下即可。比如,为匹配标记序列4 fn ['spang "whammo"] @_@,我们可以使用:

macro_rules! gibberish {
    (4 fn ['spang "whammo"] @_@) => {...};
}Run

捕获

宏模式中还可以包含捕获。这允许输入匹配在某种通用语法基础上进行,并使得结果被捕获进某个变量中。此变量可在输出中被替换使用。

捕获由$符号紧跟一个标识符(identifier)紧跟一个冒号(:)紧跟捕获种类组成。捕获种类须是如下之一:

举例来说,下列宏将其输入捕获为一个表达式:

macro_rules! one_expression {
    ($e:expr) => {...};
}Run

Rust编译器的语法转义器将保证捕获的“准确性”。一个expr捕获总是会捕获到一个对当前Rust版本来说完整、有效的表达式。

你可以将字面标记树与捕获混合使用,但有些限制(接下来将阐明它们)。

在扩展过程中,对于某捕获$name:kind,我们可以通过在expansion中写下$name来使用它。比如:

macro_rules! times_five {
    ($e:expr) => {5 * $e};
}Run

如同宏扩展本身一样,每一处捕获也都将被替换为一个完整的AST节点。也就是说,在上例中无论$e所捕获的是怎样的标记序列,它总会被解读成一个完整的表达式。

在一条模式中也可以出现多次捕获:

macro_rules! multiply_add {
    ($a:expr, $b:expr, $c:expr) => {$a * ($b + $c)};
}Run

重复

模式中可以包含重复。这使得匹配标记序列成为可能。重复的一般形式为$ ( ... ) sep rep.

重复中可以包含任意有效模式,包括字面标记树,捕获,以及其它的重复。

在扩展部分,重复也采用相同的语法。

举例来说,下述宏将每一个element都通过format!转换成字符串。它将匹配0或多个由逗号分隔的表达式,并分别将它们展开成一个Vecpush语句。

macro_rules! vec_strs {
    (
        // 重复开始:
        $(
            // 每次重复必须有一个表达式...
            $element:expr
        )
        // ...重复之间由“,”分隔...
        ,
        // ...总共重复0或多次.
        *
    ) => {
        // 为了能包含多条语句,
        // 我们将扩展部分包裹在花括号中...
        {
            let mut v = Vec::new();

            // 重复开始:
            $(
                // 每次重复将包含如下元素,其中
                // “$element”将被替换成其相应的展开...
                v.push(format!("{}", $element));
            )*

            v
        }
    };
}Run