Skip to content

P.MAC.DCL.01 定义和使用声明宏时,应保证卫生性

【描述】

  • 标识符(包括变量、泛型、生命周期名称)建议作为宏的参数传递,而不是在宏定义时硬编码,这 样可以在提升代码可维护性的同时,让宏的适用范围更广泛。

  • 在宏内引用宏外部的 item,如函数或宏时应使用绝对路径:

  1. 当宏定义和宏内引用的 item 在同一个 crate 中定义时,应该使用 $crate 元变量来指代当前 被调用宏的路径。

  2. 当宏定义和宏内引用的 item 在不同的 crate 中定义时,用 crate 名来引用。

【说明】

声明宏是半卫生宏,变量标识符在宏内外是隔离的,不会产生命名冲突,符合卫生性。但是在宏内定义 的泛型及生命周期标识符的做法是不卫生的。

卫生宏定义请参考 卫生宏 WiKi

【反例】

Rust
// 使用宏为带生命周期的类型实现 impl
macro_rules! impl_for { ($name:ty) => {
// 不符合,这里使用的 'a 是宏内部定义
  impl<'a> $name {
      fn get(&self) -> i32 { 
        *self.0
      } 
    }
  }; 
}

struct Foo<'a>(&'a i32);
// 程序能正常编译运行,但宏内外的 'a 被共用,不卫生,也不利于可维护性
impl_for!(Foo<'a>);
// ...

struct Bar<'b>(&'b i32);
// 但如果要用相同的宏为生命周期为 'b 的类型实现 impl 时 , 
// 编译会失败 : use of undeclared lifetime name `'b`
impl_for!(Bar<'b>);

【正例】

Rust
// 使用宏为带生命周期的类型实现 FooTrait
macro_rules! impl_for {
  // 符合,这里不直接使用宏内部定义的 'a ,而是通过 $lifetime 从外部传入的, 
  // 从而避免因宏不卫生引发的问题
  ($name:ty, $lifetime:tt) => {
    impl<$lifetime> for $name {
      fn get(&self) -> i32 { 
        *self.0
      } 
    }
  }; 
}

struct Foo<'a>(&'a	i32);
impl_for!(Foo<'a>,	'a); // 这里的 'a 是从宏外部传入到宏内
struct Bar<'b>(&'b	i32);
impl_for!(Bar<'b>,	'b); // 同时,宏的适用范围更加广泛

【反例】

Rust
// 宏定义在 crate a 中
#[macro_export]
macro_rules! helped {
  () => { helper!() } // This might lead to an error due to 'helper' not being in scope.
}

#[macro_export]
macro_rules! helper { 
  () => { () }
}

// 在 crate b 中使用 crate a 的两个宏
// 注意: `helper_macro::helper` 并没有导入进来
use helper_macro::helped;

fn unit() {
  // Error! 这个宏会出现问题,因为 helped 宏内部调用的 helper 宏的路径会被编译器认为是在当 前的 crate b 中
  helped!(); 
}

【正例】

Rust
// 宏定义在 crate a 中
#[macro_export]
macro_rules! helped {
  () => { $crate::helper!() }
}

#[macro_export]
macro_rules! helper {
  () => { () }
}

// 在crate b 中使用 crate a 的两个宏
// 注意: `helper_macro::helper` 并没有导入进来
use helper_macro::helped;

fn unit() {
  // OK! 这个宏能运行通过,因为 `$crate` 正确地展开成 `helper_macro` 所在的 crate a 的 路径(而不是使用者 crate b 的路径)
  helped!();
}