'Unresolved name' inside macro despite 'ident' designator

时间:2015-06-26 10:27:20

标签: 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.

1 个答案:

答案 0 :(得分:7)

好的,这个很奇怪。首先,这是我发现的工作:

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);
}

我可以说,问题在于你的原始宏正在生成一组语句,这是以某种方式混淆了编译器。将这些语句包装在一个块中似乎可以解决这个问题。

当然,然后问题是将let mut $v放入作用域使得以下println!无法访问它,所以我也修改它以返回最终值该块,然后分配给 new $v

老实说,我想不出为什么你的原始代码不应该有效。 可能是一个错误...或者它可能是macro_rules!的另一个微妙之处,我还没有掌握。这很难说。 :)