How to store a list (tuple) of types, that can be uses as arguments in another macro?

I'd like to store a list of types somehow, so that I can use those types as arguments to a macro. For instance:

macro_rules! component_types {
    () => {
      AreaOfEffect,
      BlocksTile,
      CombatStats,
      Confusion,
      Consumable,
      EventIncomingDamage,
      EventWantsToDropItem,
      EventWantsToMelee,
      EventWantsToPickupItem,
      EventWantsToUseItem,
      InBackpack,
      InflictsDamage,
      Item,
      Monster,
      Name,
      Player,
      Position,
      ProvidesHealing,
      Ranged,
      Renderable,
      SerializationHelper,
      Viewshed,
    };
}

Then these could be used by this macro (not my work):

macro_rules! serialize_individually {
  ($ecs:expr, $ser:expr, $data:expr, $( $type:ty),*) => {
      $(
      SerializeComponents::<NoError, SimpleMarker<SerializeMe>>::serialize(
          &( $ecs.read_storage::<$type>(), ),
          &$data.0,
          &$data.1,
          &mut $ser,
      )
      .unwrap();
      )*
  };
}

I imagine usage could look like this:

 serialize_individually!(ecs, serializer, data, component_types!());

However, when I do this, I get:

error: macro expansion ignores token `,` and any following
   --> src/saveload_system.rs:45:19
    |
45  |       AreaOfEffect,
    |                   ^
...
177 |         serialize_individually!(ecs, serializer, data, component_types!());
    |                                                        ------------------ caused by the macro expansion here
    |
    = note: the usage of `component_types!` is likely invalid in type context

I've tried to think about how to do this using tuples and HLists from other libraries, but haven't hit on anything yet. Is there any possible way to do this, short of bringing in an external macro system like m4?

What about using the "callback" pattern?

macro_rules! serialize_individually {
  ($ecs:expr, $ser:expr, $data:expr, $( $type:ty),* $(,)?) => {
      $(
      SerializeComponents::<NoError, SimpleMarker<SerializeMe>>::serialize(
          &( $ecs.read_storage::<$type>(), ),
          &$data.0,
          &$data.1,
          &mut $ser,
      )
      .unwrap();
      )*
  };
}

macro_rules! execute_with_type_list {
    ($name:ident!($($args:tt)*)) => {
        $name!(
            $($args)*,
            AreaOfEffect,
            BlocksTile,
            CombatStats,
            ...
        )
    }
}

fn main() {
    execute_with_type_list!(serialize_individually!(a, b, c));
}

(playground)

Note that I added a $(,)? to the end of your serialize_individually!() macro so it would accept trailing commas.

4 Likes

This is great, and made me realize I should probably take the time to go through the linked book.

Still testing this out, but it looks like $($args), should be $($args)*, in the execute_with_type_list macro (otherwise we see "error: expected one of: *, +, or ?" (also seen in the playground).

Good catch!

I saw a bunch of compiler errors mentioning "cannot find type XXX in this scope" and assumed it couldn't build because I don't have access to the rest of the codebase. Turns out the very first error message was for my macro bug :sweat_smile:

Hi @thebracket - since this was originally designed for use with your macro, just wanted to ping you in case you thought this might be worth using.