卫生性

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

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

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

  1. macro_rules! using_a {
  2. ($e:expr) => {
  3. {
  4. let a = 42;
  5. $e
  6. }
  7. }
  8. }
  9. let four = using_a!(a / 10);

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

  1. let four = {
  2. let a = 42;
  3. a / 10
  4. };

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

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

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

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

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

  1. macro_rules! using_a {
  2. ($a:ident, $e:expr) => {
  3. {
  4. let $a = 42;
  5. $e
  6. }
  7. }
  8. }
  9. let four = using_a!(a, a / 10);

此宏在展开后将变成:

  1. let four = {
  2. let a = 42;
  3. a / 10
  4. };

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