作用域

这部分最新的内容可参考 Referencescoping-exporting-and-importing 一节。部分翻译内容引自 Reference 中文版

函数式宏的作用域规则可能有一点反直觉。(函数式宏包括声明宏与函数式过程宏。) 由于历史原因,宏的作用域并不完全像各种程序项那样工作。

有两种形式的作用域:文本作用域 (textual scope) 和 基于路径的作用域 (path-based scope)。

文本作用域:基于宏在源文件中(定义和使用所)出现的顺序,或是跨多个源文件出现的顺序, 文本作用域是默认的作用域。

基于路径的作用域:与其他程序项作用域的运行方式相同。

当声明宏被 非限定标识符(unqualified identifier,非多段路径段组成的限定性路径)调用时, 会首先在文本作用域中查找。 如果文本作用域中没有任何结果,则继续在基于路径的作用域中查找。

如果宏的名称由路径限定 (qualified with a path) ,则只在基于路径的作用域中查找。

文本作用域

宏在子模块中可见

与 Rust 语言其余所有部分都不同的是,函数式宏在子模块中仍然可见。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

注意:即使子模组的内容处在不同文件中,这些例子中所述的行为仍然保持不变。

宏在定义之后可见

同样与 Rust 语言其余所有部分不同,宏只有在其定义 之后 可见。 下例展示了这一点。同时注意到,它也展示了宏不会“漏出” (leak) 其定义所在的作用域:

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

要清楚,即使你把宏移动到外层作用域,词法依赖顺序的规则依然适用。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

宏与宏之间顺序无关

然而对于宏自身来说,这种具有顺序的依赖行为不存在。 即被调用的宏可以先于调用宏之前声明:

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

宏可以被暂时覆盖

允许多次定义 macro_rules! 宏,最后声明的宏会简单地覆盖 (shadow) 上一个声明的同名宏; 如果最后声明的宏离开作用域,上一个宏在有效的作用域内还能被使用。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

#[macro_use] 属性

这个属性放置在宏定义所在的模块前 或者 extern crate 语句前。

  1. 在模块前加上 #[macro_use] 属性:导出该模块内的所有宏, 从而让导出的宏在所定义的模块结束之后依然可用。
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

注意,这可能会产生一些奇怪的后果,因为宏(包括过程宏)中的标识符只有在宏展开的过程中才会被解析。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  1. extern crate 语句加上 #[macro_use] 属性: 把外部 crate 定义且导出的宏引入当前 crate 的根/顶层模块。(当前 crate 使用外部 crate)

假设在外部名称为 mac 的 crate 中定义了 X! 宏,在当前模块:

//// 这里的 `X!` 与 `Y!` 无关,前者定义于外部 crate,后者定义于当前 crate mod a { // X!(); // defined, but Y! is undefined } macro_rules! Y { () => {}; } mod b { X!(); // defined, and so is Y! } #[macro_use] extern crate macs; mod c { X!(); // defined, and so is Y! } fn main() {}

当宏放在函数内

前四条作用域规则同样适用于函数。 至于第五条规则, #[macro_use] 属性并不直接作用于函数。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

关于宏声明的位置

由于前述种种规则,一般来说, 建议将所有应对整个 crate 均可见的宏的定义置于根模块的最顶部, 借以确保它们 一直 可用。 这个建议和适用于在文件 mod 定义的宏:

#[macro_use] mod some_mod_that_defines_macros; mod some_mod_that_uses_those_macros;

这里的顺序很重要,因为第二个模块依赖于第一个模块的宏, 所以改变这两个模块的顺序会无法编译。

基于路径的作用域

Rust 的 macro_rules! 宏 默认并没有基于路径的作用域。

然而,如果这个宏被加上 #[macro_export] 属性,那么它就在 crate 的根作用域里被定义, 而且能直接使用它。

导入/导出宏 一章会更深入地探讨这个属性。