Rust 声明式宏和 tt 的作用域
Rust Declarative Macros and Scoping of tt
我在尝试将函数应用于宏内的表达式时遇到了这个问题。我将它浓缩为这个有点愚蠢的最小工作示例,其中我们在花括号内有一个表达式并对其应用一个函数,如下所示:
macro_rules! foo {
( {$expression:expr} .apply($($f:tt)+)) => {
{
let apply_func = $($f)+;
apply_func($expression)
}
}
}
对于我的用例,我实际上是将该函数应用于多个表达式,但这在这里并不重要。问题是我将局部变量 apply_func
绑定到 apply(...)
内的标记。我很确定本地 let 绑定会做我想做的,我很高兴地说以下表达式都按预期工作:
//WORKS: simple use case
println!("{}", foo!({10}.apply(|x|x*x)));
//WORKS: nested use case
println!("{}", foo!({ foo!({10}.apply(|x|x*x)) }.apply(|x|x+3)));
//WORKS: using some function to pass to the macro
let some_func = |x|x+10;
println!("{}", foo!({ foo!({10}.apply(some_func)) }.apply(|x|x+3)));
//WORKS: calling a function in this scope `apply_func` and using it
//from the inner macro invocation
{
let apply_func = |x|x*3;
println!("{}", foo!({ foo!({10}.apply(apply_func)) }.apply(|x|x+3)));
}
结果输出为
100
103
53
33
符合预期。
但是,如果我像这样给出apply_func
作为内部宏调用的参数,问题就会出现:
// FAILS: trying to access the apply_func of the outer macro from
// the inner macro without defining it in this scope
println!("{}", foo!({ foo!({10}.apply(apply_func)) }.apply(|x|x+3)));
我担心这会扩展到
{
let apply_func = |x| x + 3;
apply_func({
let apply_func = apply_func;
apply_func(10)
})
}
我一点都不喜欢,因为我不希望嵌套范围的 apply_func
实例之间有任何交互。但是在我的机器上(rustc 1.48.0 (7eac88abb 2020-11-16)
)它给出了一个编译错误
| println!("{}", foo!({ foo!({10}.apply(apply_func)) }.apply(|x|x+3)));
| ^^^^^^^^^^ not found in this scope
太棒了。 Try it on the Rust Playground.
我的问题是:我可以依赖这种行为吗?这真的是预期的吗?有人可以向我解释一下吗?
我知道 Rust 宏不是 C 风格的文本替换。传递给宏的标记是否需要在“粘贴”到宏扩展之前是有效代码?
我会说是的,你可以依赖这种行为。
Rust 宏是卫生的,这意味着编译器会处理由宏创建的标识符,而不是与在宏调用之外创建的标识符发生冲突。
在您的情况下,宏创建的 apply_func
将被视为与您在宏外声明然后提供给宏的标识符完全不同的标识符。
我在尝试将函数应用于宏内的表达式时遇到了这个问题。我将它浓缩为这个有点愚蠢的最小工作示例,其中我们在花括号内有一个表达式并对其应用一个函数,如下所示:
macro_rules! foo {
( {$expression:expr} .apply($($f:tt)+)) => {
{
let apply_func = $($f)+;
apply_func($expression)
}
}
}
对于我的用例,我实际上是将该函数应用于多个表达式,但这在这里并不重要。问题是我将局部变量 apply_func
绑定到 apply(...)
内的标记。我很确定本地 let 绑定会做我想做的,我很高兴地说以下表达式都按预期工作:
//WORKS: simple use case
println!("{}", foo!({10}.apply(|x|x*x)));
//WORKS: nested use case
println!("{}", foo!({ foo!({10}.apply(|x|x*x)) }.apply(|x|x+3)));
//WORKS: using some function to pass to the macro
let some_func = |x|x+10;
println!("{}", foo!({ foo!({10}.apply(some_func)) }.apply(|x|x+3)));
//WORKS: calling a function in this scope `apply_func` and using it
//from the inner macro invocation
{
let apply_func = |x|x*3;
println!("{}", foo!({ foo!({10}.apply(apply_func)) }.apply(|x|x+3)));
}
结果输出为
100
103
53
33
符合预期。
但是,如果我像这样给出apply_func
作为内部宏调用的参数,问题就会出现:
// FAILS: trying to access the apply_func of the outer macro from
// the inner macro without defining it in this scope
println!("{}", foo!({ foo!({10}.apply(apply_func)) }.apply(|x|x+3)));
我担心这会扩展到
{
let apply_func = |x| x + 3;
apply_func({
let apply_func = apply_func;
apply_func(10)
})
}
我一点都不喜欢,因为我不希望嵌套范围的 apply_func
实例之间有任何交互。但是在我的机器上(rustc 1.48.0 (7eac88abb 2020-11-16)
)它给出了一个编译错误
| println!("{}", foo!({ foo!({10}.apply(apply_func)) }.apply(|x|x+3)));
| ^^^^^^^^^^ not found in this scope
太棒了。 Try it on the Rust Playground.
我的问题是:我可以依赖这种行为吗?这真的是预期的吗?有人可以向我解释一下吗?
我知道 Rust 宏不是 C 风格的文本替换。传递给宏的标记是否需要在“粘贴”到宏扩展之前是有效代码?
我会说是的,你可以依赖这种行为。
Rust 宏是卫生的,这意味着编译器会处理由宏创建的标识符,而不是与在宏调用之外创建的标识符发生冲突。
在您的情况下,宏创建的 apply_func
将被视为与您在宏外声明然后提供给宏的标识符完全不同的标识符。