在Rust中创建宏的闭包环境

Iva*_*van 8 rust rust-macros

我正在尝试实现这样的(简化):

macro_rules! atest {
    ($closure:tt) => {
        let x = 5;
        println!("Result is {}", $closure())
    };
}

fn main() {
    //let x = 50;
    atest!((|| 5 + x));
}
Run Code Online (Sandbox Code Playgroud)

它不起作用,因为atest在宏评估之前编译器会考虑宏的参数:

error[E0425]: cannot find value `x` in this scope
  --> src/main.rs:10:20
   |
10 |     atest!((|| 5 + x));
   |                    ^ not found in this scope
Run Code Online (Sandbox Code Playgroud)

是否有可能使这项工作?我的理解是在编译之前扩展了宏.

Pet*_*all 5

是否有可能使这项工作?我的理解是在编译之前扩展了宏?

宏在编译之前会扩展,但在解析之前不会扩展.原始输入代码已经被解析,宏在抽象语法树上运行,而不是在文本上运行.例如,闭包已被理解为闭包,其自由变量已经绑定到其词法范围中的变量.

这与其他一些语言宏不同,例如C/C++,它们在原始文本上运行,如果你不小心的话,让你搞砸了.


tre*_*tcl 5

彼得的回答解释了为什么您的工作行不通。这是所谓的“宏卫生”的一部分:在宏中声明的内容不能“泄漏”到周围的范围中。

解决您面临的问题的一种常见方法是将标识符的名称作为另一个参数传递给宏:

macro_rules! atest {
    ($x:ident, $closure:tt) => {
        let $x = 5;
        println!("Result is {}", $closure())
    };
}

fn main() {
    atest!(x, (|| 5 + x));
}
Run Code Online (Sandbox Code Playgroud)

这将起作用,因为x即使声明在宏内,命名也将其置于调用者的范围内。

您可能会注意到闭包是不必要的,至少在此示例中-您可以将其5 + x作为表达式传递给宏并将其内联扩展。

macro_rules! atest {
    ($x:ident, $value:expr) => {
        let $x = 5;
        println!("Result is {}", $value)
    };
}
Run Code Online (Sandbox Code Playgroud)

您将此宏称为atest!(x, 5 + x),看起来有点像其自身的封闭。那可能会让您想到编写的想法atest!(|x| 5 + x)。这也将起作用,并且变量的作用域为闭包:

macro_rules! atest {
    ($closure:expr) => {
        let x = 5;
        println!("Result is {}", $closure(x))
    };
}
Run Code Online (Sandbox Code Playgroud)

参考文献