在 Rust 的宏中创建闭包环境

12

我想实现类似于这样的效果(简化版):

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

这是否有可能实现?我的理解是宏在编译之前就被展开了。

2个回答

13

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

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

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)
    };
}

你可以像这样调用宏 atest!(x, 5 + x),看起来有点像自己的闭包。这可能会让你想要写成 atest!(|x| 5 + x)。这也可以工作,并且闭包中会有一个变量作用域:

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

参考资料


5

这个能够工作吗?我的理解是宏在编译之前被展开了?

宏在编译之前被展开,但不是在语法分析之前。原始输入代码已经被解析了,宏操作的是抽象语法树而不是文本。例如,闭包已经被理解为闭包,并且它的自由变量已经绑定到其词法作用域中的变量。

这与其他一些语言的宏不同,例如C/C++,后者作用于原始文本,如果不小心使用,可能会造成严重的问题。


谢谢,我知道宏是在AST上工作的,但没有意识到自由变量引用是如何处理的。我以为宏片段会在绑定变量之前适合AST的正确位置。但是trentcl的解决方案非常好 - 在宏中添加:ident参数,将引入的变量放置在正确的位置。 - Ivan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接