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!

卫生性

Rust宏是部分卫生的。具体来说,对于绝大多数标识符,它是卫生的;但对泛型参数和生命周期来算,它不是。

之所以能做到“卫生”,在于每个标识符都被赋予了一个看不见的“句法上下文”。在比较两个标识符时,只有在标识符的明面名字和句法上下文一致的情况下,两个标识符才能被视作等同。

为阐释这一点,考虑下述代码:

macro_rules! using_a {
    ($e:expr) => {
        {
            let a = 42;
            $e
        }
    }
}

let four = using_a!(a / 10);

我们将采用背景色来表示句法上下文。现在,将上述宏调用展开如下:

let four = {
    let a = 42;
    a / 10
};

首先记起,macro_rules!的调用在展开过程中等同于消失。

其次,如果我们现在就尝试编译上述代码,编译器将报如下错误:

<anon>:11:21: 11:22 error: unresolved name `a`
<anon>:11 let four = using_a!(a / 10);

注意到宏在展开后背景色(即其句法上下文)发生了改变。每处宏展开均赋予其内容一个新的、独一无二的上下文。故而,在展开后的代码中实际上存在两个不同的a,分别有不同的句法上下文。即,aa并不相同,即它们便看起来很像。

尽管如此,被替换进宏展开中的标记仍然保持着它们原有的句法上下文(因它们是被提供给这宏的,并非这宏本身的一部分)。因此,我们作出如下修改:

macro_rules! using_a {
    ($a:ident, $e:expr) => {
        {
            let $a = 42;
            $e
        }
    }
}

let four = using_a!(a, a / 10);

此宏在展开后将变成:

let four = {
    let a = 42;
    a / 10
};

因为只用了一种a,编译器将欣然接受此段代码。