Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

'Unresolved name' inside macro despite 'ident' designator

Tags:

rust

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.

like image 443
jtepe Avatar asked Jun 26 '15 10:06

jtepe


1 Answers

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. :)

like image 141
DK. Avatar answered Oct 05 '22 10:10

DK.