dragostis
dragostis

Reputation: 2662

Why does Rust not find variables in multi-statement macros?

I have a macro that gets called inside an unsafe block which has the following pattern:

( $mrb:expr, $sig:expr, $args:ident, $name:ident : $t:tt) => {
    let $args = uninitialized::<*const MRValue>();
    let count = uninitialized::<i32>();

    mrb_get_args($mrb, $sig, args!($name, $t), &$args as *const *const MRValue,
                 &count as *const i32);
};

I've expanded the macro with --pretty expanded,hygiene which gives me:

let args /* 77#30 */ =
    uninitialized /* 789#28
        */::<*const MRValue /* 793#28 */>();
 let count /* 807#31 */ =
     uninitialized /* 789#28 */::<i32 /* 68#28 */>();
 mrb_get_args /* 805#28
     */(mrb /* 804#29 */, sig /* 797#29 */,
        &v /* 76#33 */ as *const i32 /* 68#34 */,
        &args /* 77#27 */ as
            *const *const MRValue /* 793#28 */,
        &count /* 807#28 */ as *const i32 /* 68#28 */);

args appear to be the same (77) and count also appear to be the same (807), but I'm getting the following error nonetheless:

<mrusty macros>:24:20: 24:21 error: unresolved name `args`. Did you mean the macro `args!`? [E0425]
<mrusty macros>:24 mrb , sig , $ args , $ ( $ name : $ t ) , * ) ; conv ! (
                                  ^
<mrusty macros>:23:29: 24:48 note: in this expansion of args_rest! (defined in <mrusty macros>)
src/main.rs:12:47: 16:7 note: in this expansion of mrfn! (defined in <mrusty macros>)
<mrusty macros>:24:20: 24:21 help: run `rustc --explain E0425` to see a detailed explanation
<mrusty macros>:6:5: 6:10 error: unresolved name `count` [E0425]
<mrusty macros>:6 , & count as * const i32 ) ; } ; (
                  ^~~~~
<mrusty macros>:23:29: 24:48 note: in this expansion of args_rest! (defined in <mrusty macros>)
src/main.rs:12:47: 16:7 note: in this expansion of mrfn! (defined in  <mrusty macros>)
<mrusty macros>:6:5: 6:10 help: run `rustc --explain E0425` to see a detailed explanation

This looks fishy and it appears to be a bug, but I'd like another pair of eyes over this before I submit an issue on Rust.

Upvotes: 4

Views: 171

Answers (2)

dragostis
dragostis

Reputation: 2662

The basic reason why this doesn't work is because multi-statement macros are broken. Any macros that don't return a value (e.g. a block) will only return the first statement.

macro_rules! example {
    ( $name:ident ) => {
        let mut $name = 0;
        let mut $name = 1;
    }
}

fn main() {
    example!(x);

    println!("{}", x);
}

This example prints 0, not 1. The bug has been closed, though, at it will probably land in Rust 1.10.

In the meanwhile, use blocks where applicable.

Upvotes: 0

Vladimir Matveev
Vladimir Matveev

Reputation: 127941

Try this:

( $mrb:expr, $sig:expr, $args:ident, $name:ident : $t:tt) => {{
    let $args = uninitialized::<*const MRValue>();
    let count = uninitialized::<i32>();

    mrb_get_args($mrb, $sig, args!($name, $t), &$args as *const *const MRValue,
                 &count as *const i32);
}};

(note that the body of the macro expansion is wrapped into the second set of braces)

I don't remember the exact reason why you need this, but the basic idea is that each statement in the macro expansion block is expanded with its own hygiene context, therefore $args in the first line is not the same as $args in the last line. If you put all the statements into a single block, however, the hygiene context becomes shared, and both expansions of $args now refer to the same identifier. So, this is likely not a bug; it's just how macro expansion in Rust works.

Upvotes: 4

Related Questions