我正在尝试实现这样的(简化):
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
是否有可能使这项工作?我的理解是在编译之前扩展了宏。
Peter's answer解释了为什么你所做的不起作用。这是所谓的“宏观卫生”的一部分:在宏内部声明的事物不能“泄漏”到周围的范围内。
您遇到的问题的常见解决方法是将标识符的名称作为另一个参数传递给宏:
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))
};
}
是否有可能使这项工作?我的理解是在编译之前扩展了宏?
宏在编译之前会扩展,但在解析之前不会扩展。原始输入代码已经被解析,宏在抽象语法树上运行,而不是在文本上运行。例如,闭包已被理解为闭包,其自由变量已经绑定到其词法范围中的变量。
这与其他一些语言宏不同,例如C/C++,它们在原始文本上运行,如果你不小心的话,让你搞砸了。