2

When expanding arguments to a macro, is there a way to include the argument number within the macro

Here is a complete example showing how this might be used to assign an index to a struct using a trait. Currently struct_number() always returns 0, could this be made to return a constant number based on the order of the argument to the macro?

struct Foo {_var: bool}
struct Bar {_var: u8}
struct Baz {_var: i16}

trait NumberStruct {
    fn struct_number() -> usize;
}

macro_rules! number_structs_impl {
    ($($t:ty)*) => ($(
        impl NumberStruct for $t {
            fn struct_number() -> usize {
               // How to return a number based on the argument order?
                return 0;
            }
        }
    )*)
}

number_structs_impl!(Foo Bar Baz);

fn main() {
    // see if the numbers are correct
    macro_rules! print_numbers {
        ($($t:tt)*) => ($(
            print!("{}:{} ", stringify!($t), $t::struct_number());
        )*)
    }

    // should print:
    // Baz:2 Bar:1 Foo:0
    print_numbers!(Baz Bar Foo);
    println!();
}
ideasman42
  • 42,413
  • 44
  • 197
  • 320

2 Answers2

0

This can be done:

  • First counting all arguments.
  • Using a recursive macro, so the tail* arguments can be counted.
  • Optionally storing the list of structs in a macro so both macro invocations don't need to repeat the list.

Working example:

struct Foo {_var: bool}
struct Bar {_var: u8}
struct Baz {_var: i16}

trait NumberStruct {
    fn struct_number() -> usize;
}

macro_rules! count_tts {
    () => {0usize};
    ($_head:tt $($tail:tt)*) => {1usize + count_tts!($($tail)*)};
}

macro_rules! number_structs_impl {
    () => {};
    ($head:tt $($tail:tt)*) => {
        impl NumberStruct for $head {
            fn struct_number() -> usize {
                return STRUCT_NUM - (1 + count_tts!($($tail)*));
            }
        }
        number_structs_impl!($($tail)*);
    };
}

// avoid repeating same structs
macro_rules! apply_structs {
    ($macro_id:ident) => (
        $macro_id! {
            Foo
            Bar
            Baz
        }
    )
}

const STRUCT_NUM: usize = apply_structs!(count_tts);
apply_structs!(number_structs_impl);

fn main() {
    // see if the numbers are correct
    macro_rules! print_numbers {
        ($($t:tt)*) => ($(
            print!("{}:{} ", stringify!($t), $t::struct_number());
        )*)
    }

    // should print:
    // Baz:2 Bar:1 Foo:0
    print_numbers!(Baz Bar Foo);
    println!();
}

Note: I'm posting this answer to show its possible, however it is a bit of a messy solution since it involves passing macros to macros and two invocations, this could be done more cleanly if recursive macros could be expanded, taking the last argument each recursion, see related question.

Community
  • 1
  • 1
ideasman42
  • 42,413
  • 44
  • 197
  • 320
0

An approach to defining numbered implementations is to use a recursive macro. A unique number can be created by counting arguments, in this case counting trailing arguments.

The problem with this, is the indices are reversed where the first struct has the largest number, the last struct zero.

If you only need the numbers to be unique, it wont matter, however in this case I want each structs index to match the order its passed to the macro.

Input arguments can be reversed using a recursive macro, see this example.

Using this macro, its possible to write a generic macro:

apply_args_reverse!(macro_name, arg1 arg2 arg3)

Which expands into:

macro_name!(arg3 arg2 arg1)

Of course thats not very useful on its own, but it can be useful if the arguments aren't written directly, but passed as arguments.

This can be used to create make a macro that expands with the number of each argument as follows:

struct Foo {_var: bool}
struct Bar {_var: u8}
struct Baz {_var: i16}

trait NumberStruct {
    fn struct_number() -> usize;
}

macro_rules! count_args_space {
    () => {0_usize};
    ($_head:tt $($tail:tt)*) => {1_usize + count_args_space!($($tail)*)};
}

macro_rules! number_structs_impl {
    (@single $t:tt $($tail:tt)*) => (
        impl NumberStruct for $t {
            fn struct_number() -> usize {
                return count_args_space!($($tail)*);
            }
        }
    );

    () => {};
    ($head:tt $($tail:tt)*) => {
        number_structs_impl!(@single $head $($tail)*);
        number_structs_impl!($($tail)*);
    };
}

macro_rules! apply_args_reverse {
    ($macro_id:tt [] $($reversed:tt)*) => {
        $macro_id!($($reversed) *);
    };
    ($macro_id:tt [$first:tt $($rest:tt)*] $($reversed:tt)*) => {
        apply_args_reverse!($macro_id [$($rest)*] $first $($reversed)*);
    };
    // Entry point, use brackets to recursively reverse above.
    ($macro_id:tt, $($t:tt)*) => {
        apply_args_reverse!($macro_id [ $($t)* ]);
    };
}

// Note that both commands below work, and can be swapped to reverse argument order.

// number_structs_impl!(Foo Bar Baz);
apply_args_reverse!(number_structs_impl, Foo Bar Baz);

fn main() {
    // see if the numbers are correct
    macro_rules! print_numbers {
        ($($t:tt)*) => ($(
            print!("{}:{} ", stringify!($t), $t::struct_number());
        )*)
    }

    print_numbers!(Baz Bar Foo);
    println!();
}

Notice the statements:

number_structs_impl!(Foo Bar Baz);

... and

apply_args_reverse!(number_structs_impl, Foo Bar Baz);

... are interchangeable, swapping which is commented reverses the order of numbers assigned to each struct.


Note: keeping my other answer, while this is more concise, it's also more fragile, prone to hard-to-troubleshoot problems, since macro expansion gets deeply nested (I found this while getting it to work at least).

Community
  • 1
  • 1
ideasman42
  • 42,413
  • 44
  • 197
  • 320