How do I generate an array with macro! by applying a function to each element?

Viewed 50

I want to generate a static array (doing this at runtime is not an option) with help of a macro.

My attempts are

macro_rules! test {
    ($($data:expr),*)   => {
    [ test!(@xform $($data),*) ]
    };

    (@xform)            => { };
    (@xform $a:expr)        => { be32($a) };
    (@xform $a:expr, $($data:expr),*)   => { be32($a), test!(@xform $($data),*) };
}

// just for simplicity...
const fn be32(v: u32) -> u32 { v + 1 }

static FILE_HEADER_0: [u32;2] = test!(1, 2);
static FILE_HEADER_1: [u32;2] = [be32(1), be32(2)];

but this fails with

error: macro expansion ignores token `,` and any following
 --> src/lib.rs:8:52
  |
3 |     [ test!(@xform $($data),*) ]
  |       ------------------------- help: you might be missing a semicolon here: `;`
  |       |
  |       caused by the macro expansion here
...
8 |     (@xform $a:expr, $($data:expr),*)    => { be32($a), test!(@xform $($data),*) };
  |                                                       ^
  |
  = note: the usage of `test!` is likely invalid in expression context

I expect that FILE_HEADER_0 is generated like FILE_HEADER_1

Is this possible with normal macro_rules! or do I have to use proc_macro?

1 Answers

Macros cannot expand to multiple items. They have to generate only one. Your macro expands to multiple comma-separated expressions, and this is invalid.

The usual solution to that is push-down accumulation: accumulate the resulting array in the macro and keep adding elements to it:

macro_rules! test {
    ($($data:expr),*) => {
        test!(@xform [] $($data),*)
    };

    (@xform $arr:tt) => { $arr };
    (@xform [ $($arr:tt)* ] $a:expr) => {
        test!(@xform [ $($arr)* be32($a) ])
    };
    (@xform [ $($arr:tt)* ] $a:expr, $($data:expr),*) => {
        test!(@xform [ $($arr)* be32($a), ] $($data),*)
    };
}

In the first expansion we will have test!(@xform [] 1, 2). This will reach the last arm and expand to test!(@xform [ be(1), ] 2). This will expand (by the third arm) to test!(@xform [ be(1), be(2) ]) which will finally expand to [be(1), be(2)] by the second arm.

In this case, however, you don't need complex solutions. A simple macro will do:

macro_rules! test {
    ($($data:expr),*) => {
        [ $( be32($data) ),* ]
    };
}
Related