I'm playing around with the Rust macro system in order to learn more about how it works. I wrote a simple macro which takes an identifier and adds a list of numbers to it.
macro_rules! do_your_thing {
( $v:ident; $($e:expr),+ ) => {
let mut $v = 0;
$(
$v += $e;
)+
};
}
fn main() {
do_your_thing!(i; 3, 3, 3);
println!("{}", i);
}
If I run this program, the compiler will complain three times 'i' being an unresolved name for every repetition inside the macro for '$v += $e;'
<anon>:5:13: 5:15 error: unresolved name `i`
<anon>:5 $v += $e;
^
I know that macros in Rust are hygienic. That is why I used the ident
designator. Is it possible that there is an additional syntactic context for the repetition $(...)+
?
UPDATE
After DK.'s answer I did a little digging and found the hygiene
argument for the --pretty
option. Basically, it annotates the syntax contexts after macro expansion happened. After running
rustc -Z unstable-options --pretty expanded,hygiene main.rs
on my initial program it gave me the following output
fn main /* 67#0 */() {
let mut i /* 68#4 */ = 0;
i /* 68#3 */ += 3;
i /* 68#3 */ += 3;
i /* 68#3 */ += 3;
}
Running the same command on DK.'s modifications resulted in
fn main /* 67#0 */() {
let i /* 68#4 */ =
{
let mut i /* 68#5 */ = 0;
i /* 68#5 */ += 3;
i /* 68#5 */ += 3;
i /* 68#5 */ += 3;
i /* 68#5 */
};
}
So, the $(...)+ inside the macro did in fact introduce a new syntax context in my original macro. However, wrapping it in a block, as DK did, somehow prevented that from happening. Instead a new syntax context was introduced for the whole block.
Ok, this one is weird. First, here's what I found would work:
macro_rules! do_your_thing {
( $v:ident; $($e:expr),+ ) => {
let mut $v = {
let mut $v = 0;
$(
$v += $e;
)+
$v
};
};
}
fn main() {
do_your_thing!(i; 3, 3, 3);
println!("{}", i);
}
The problem, near as I can tell, is that your original macro was producing a set of statements, and this was somehow confusing the compiler. Wrapping those statements in a block appears to fix this.
Of course, then the problem is that putting let mut $v
into a scope makes it inaccessible to the following println!
, so I also modified it to return the final value from the block, which is then assigned to a new $v
.
Honestly, I can't think of why your original code shouldn't have worked. It might be a bug... or it might be yet another subtlety of macro_rules!
that I haven't gotten a hold of yet. It's hard to tell. :)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With