I encountered this problem while trying to apply a function to expressions inside a macro. I condensed it down to this somewhat silly minimal working example, where we have an expression inside curly braces and apply a function to it like so:
macro_rules! foo {
( {$expression:expr} .apply($($f:tt)+)) => {
{
let apply_func = $($f)+;
apply_func($expression)
}
}
}
For my use case I am actually applying the function to multiple expressions, but that is not important here. The thing is that I bind a local variable apply_func to the tokens inside the apply(...). I was pretty sure that the local let binding would do exactly what I want and I am happy to say that the following expressions all work as intended:
//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)));
}
The resulting output is
100
103
53
33
as expected.
However, the problem manifests if I give apply_func as the argument of the inner macro invocation like so:
// 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)));
I feared that this could expand to
{
let apply_func = |x| x + 3;
apply_func({
let apply_func = apply_func;
apply_func(10)
})
}
which I would not have liked one bit, because I want no interaction between apply_func instances of the nested scopes. But on my machine (with rustc 1.48.0 (7eac88abb 2020-11-16)) it gives a compile error
| println!("{}", foo!({ foo!({10}.apply(apply_func)) }.apply(|x|x+3)));
| ^^^^^^^^^^ not found in this scope
which is fantastic. Try it on the Rust Playground.
My questions are: Can I rely on that behavior? Is that actually expected and can someone explain that to me?
I know that Rust macros are not C-style text replacement. Is it the case that the tokens passed to the macro need to be valid code before being "pasted" into the macro expansion?