简体   繁体   中英

'Unresolved name' inside macro despite 'ident' designator

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM