11

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

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

fn main() {
    //let x = 50;
    atest!((|| 5 + x));
}

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

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

有可能完成这项工作吗?我的理解是宏在编译之前被扩展。

4

2 回答 2

8

彼得的回答解释了为什么你正在做的事情不起作用。这是所谓的“宏卫生”的一部分:在宏中声明的东西不能“泄漏”到周围的范围内。

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

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

fn main() {
    atest!(x, (|| 5 + x));
}

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

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

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

你称这个宏为 like atest!(x, 5 + x),它看起来有点像它自己的闭包。这可能会让你产生写作的想法atest!(|x| 5 + x)。这也将起作用,变量范围为闭包:

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

参考

于 2018-05-15T11:33:02.057 回答
5

有可能完成这项工作吗?我的理解是宏在编译之前被扩展?

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

这与其他一些语言宏不同,例如C/C++,它们对原始文本进行操作,如果你不小心的话,会让你把事情搞砸。

于 2018-05-13T17:57:39.410 回答