作用域

宏作用域的决定方式可能有一点反直觉。首先就与语言剩下的所有部分都不同的是,宏在子模组中仍然可见。

  1. macro_rules! X { () => {}; }
  2. mod a {
  3. X!(); // 已被定义
  4. }
  5. mod b {
  6. X!(); // 已被定义
  7. }
  8. mod c {
  9. X!(); // 已被定义
  10. }
  11. # fn main() {}

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

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

  1. mod a {
  2. // X!(); // 未被定义
  3. }
  4. mod b {
  5. // X!(); // 未被定义
  6. macro_rules! X { () => {}; }
  7. X!(); // 已被定义
  8. }
  9. mod c {
  10. // X!(); // 未被定义
  11. }
  12. # fn main() {}

需要阐明的是,即便宏定义被移至外围域,此顺序依赖行为仍旧不变:

  1. mod a {
  2. // X!(); // 未被定义
  3. }
  4. macro_rules! X { () => {}; }
  5. mod b {
  6. X!(); // 已被定义
  7. }
  8. mod c {
  9. X!(); // 已被定义
  10. }
  11. # fn main() {}

然而,对于宏们自身来说,此依赖行为不存在:

  1. mod a {
  2. // X!(); // 未被定义
  3. }
  4. macro_rules! X { () => { Y!(); }; }
  5. mod b {
  6. // X!(); // 已被定义, 但Y!未被定义
  7. }
  8. macro_rules! Y { () => {}; }
  9. mod c {
  10. X!(); // 均已被定义
  11. }
  12. # fn main() {}

可通过 #[macro_use]属性将宏导出模组:

  1. mod a {
  2. // X!(); // 未被定义
  3. }
  4. #[macro_use]
  5. mod b {
  6. macro_rules! X { () => {}; }
  7. X!(); // 已被定义
  8. }
  9. mod c {
  10. X!(); // 已被定义
  11. }
  12. # fn main() {}

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

  1. mod a {
  2. // X!(); // 未被定义
  3. }
  4. #[macro_use]
  5. mod b {
  6. macro_rules! X { () => { Y!(); }; }
  7. // X!(); // 已被定义,但Y!并未被定义
  8. }
  9. macro_rules! Y { () => {}; }
  10. mod c {
  11. X!(); // 均已被定义
  12. }
  13. # fn main() {}

让情形变得更加复杂的是,当#[macro_use]被作用于extern crate时,其行为又会发生进一步变化:此类声明从效果上看,类似于被放在了整个模组的顶部。因此,假设在某个extern crate mac中定义了X!,则有:

  1. mod a {
  2. // X!(); // 已被定义,但Y!并未被定义
  3. }
  4. macro_rules! Y { () => {}; }
  5. mod b {
  6. X!(); // 均已被定义
  7. }
  8. #[macro_use] extern crate macs;
  9. mod c {
  10. X!(); // 均已被定义
  11. }
  12. # fn main() {}

最后,注意这些有关作用域的行为同样适用于函数,除了#[macro_use]以外(它并不适用):

  1. macro_rules! X {
  2. () => { Y!() };
  3. }
  4. fn a() {
  5. macro_rules! Y { () => {"Hi!"} }
  6. assert_eq!(X!(), "Hi!");
  7. {
  8. assert_eq!(X!(), "Hi!");
  9. macro_rules! Y { () => {"Bye!"} }
  10. assert_eq!(X!(), "Bye!");
  11. }
  12. assert_eq!(X!(), "Hi!");
  13. }
  14. fn b() {
  15. macro_rules! Y { () => {"One more"} }
  16. assert_eq!(X!(), "One more");
  17. }
  18. #
  19. # fn main() {
  20. # a();
  21. # b();
  22. # }

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