Is it possible to declare variables procedurally using Rust macros?

There is also https://github.com/dtolnay/paste, which works well in casese where concat_idents is underpowered or in cases where you can't target the nightly compiler.

macro_rules! foo_macro {
    ( $( $name:ident ),+ ) => {
        paste::item! {
            #[test]
            fn [<test_ $name>]() {
                assert! false
            }
        }
    };
}

In cases where concat_idents doesn't work (which is most cases I'd like to use it) changing the problem from concatenated identifiers to using namespaces does work.

That is, instead of the non-working code:

macro_rules! test {
    ($x:ident) => ({
        struct concat_idents!(hello_, $x) {}
        enum contact_idents!(hello_, $x) {}
    })
}

The user can name the namespace, and then have preset names as shown below:

macro_rules! test {
    ($x:ident) => ({
        mod $x {
            struct HelloStruct {}
            enum HelloEnum {}
        }
    })
}

Now you have a name based on the macro's argument. This technique is only helpful in specific cases.


Yes however this is only available as a nightly-only experimental API which may be removed.

You can pass arbitrary identifier into a macro and yes, you can concatenate identifiers into a new identifier using concat_idents!() macro:

#![feature(concat_idents)]

macro_rules! test {
    ($x:ident) => ({
        let z = concat_idents!(hello_, $x);
        z();
    })
}

fn hello_world() {  }

fn main() {
    test!(world);
}

However, as far as I know, because concat_idents!() itself is a macro, you can't use this concatenated identifier everywhere you could use plain identifier, only in certain places like in example above, and this, in my opinion, is a HUGE drawback. Just yesterday I tried to write a macro which could remove a lot of boilerplate in my code, but eventually I was not able to do it because macros do not support arbitrary placement of concatenated identifiers.

BTW, if I understand your idea correctly, you don't really need concatenating identifiers to obtain unique names. Rust macros, contrary to the C ones, are hygienic. This means that all names of local variables introduced inside a macro won't leak to the scope where this macro is called. For example, you could assume that this code would work:

macro_rules! test {
    ($body:expr) => ({ let x = 10; $body })
}

fn main() {
    let y = test!(x + 10);
    println!("{}", y);
}

That is, we create a variable x and put an expression after its declaration. It is then natural to think that x in test!(x + 10) refers to that variable declared by the macro, and everything should be fine, but in fact this code won't compile:

main3.rs:8:19: 8:20 error: unresolved name `x`.
main3.rs:8     let y = test!(x + 10);
                             ^
main3.rs:3:1: 5:2 note: in expansion of test!
main3.rs:8:13: 8:27 note: expansion site
error: aborting due to previous error

So if all you need is uniqueness of locals, then you can safely do nothing and use any names you want, they will be unique automatically. This is explained in macro tutorial, though I find the example there somewhat confusing.

Tags:

Macros

Rust