Skip to content

P.MAC.PRO.01 定义和使用过程宏时,应保证卫生性

【描述】

过程宏在展开时,有时可能会直接引用输入代码中的标识符(变量、函数名等)。如果过程宏引入了与周围代码冲突的标识符,就会导致不卫生的情况。为了避免这种情况,通常需要对输入标识符进行重命名。

要避免不卫生的情况,包括以下常用方法:

a. 确保宏生成代码的符号与外部有差异,如增加_前缀。

b. 对库中程序项使用绝对路径.

【反例】

Rust
// crate guide
#[proc_macro]
pub fn my_macro(input: TokenStream) -> TokenStream { 
  let expanded = quote! {
    // 不符合:直接使用外部的变量 x
    x = 999;
    // 不符合: 宏内定义的变量 local 被外部使用
    let local = "hello";

    // ...
  };
  expanded.into() 
}

// crate b
fn main() {
  let mut x = 0;  
  let local = "";

  guide::my_macro!();

  assert_eq!(x, 999);
  assert_eq!(local, "hello"); 
}

【正例】

Rust
#[proc_macro]
pub fn my_macro(input: TokenStream) -> TokenStream { 
  let expanded = quote! {
    // 符合:方法 a. 宏内变量增加 `    ` 前缀
    let _x = 0;
    let _local = "hello";

    // ...
  };
  expanded.into() 
}

【正例】

过程宏生成的代码宜使用绝对路径,防止命名冲突产生意想不到的后果。 可以配合使用 #![no_implicit_prelude] 属性来验证标识符是否都使用了绝对路径:

Rust
#![no_implicit_prelude]

#[derive(MyMacro)] 
struct A;
Rust
// 符合:方法 b. 使用绝对路径
quote!(::std::string::ToString::to_string(a))
Rust
quote! {{
  // 符合:方法 b. 使用绝对路径
  use ::std::string::ToString;
  a.to_string() 
}}