P.MAC.DCL.01 定义和使用声明宏时,应保证卫生性
【描述】
标识符(包括变量、泛型、生命周期名称)建议作为宏的参数传递,而不是在宏定义时硬编码,这 样可以在提升代码可维护性的同时,让宏的适用范围更广泛。
在宏内引用宏外部的 item,如函数或宏时应使用绝对路径:
当宏定义和宏内引用的 item 在同一个 crate 中定义时,应该使用 $crate 元变量来指代当前 被调用宏的路径。
当宏定义和宏内引用的 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!();
}