I've defined a set of enums for different categories of weapons in my Rust application using a macro. Each enum variant is associated with a tuple containing values for weight, damage, and value. Here's the macro and its usage:
macro_rules! define_category_enum {
(
$trait_name:ident { $( $field_name:ident : $field_ty:ty ),* };
$($category:ident {
$($variant:ident
($( $weight_val:expr, $damage_val:expr, $value_val:expr ),* )
),* $(,)?
} );* $(;)?
) => {
pub trait $trait_name {
$(fn $field_name(&self) -> $field_ty;)*
}
$(#[derive(Debug, Clone, PartialEq)]
pub enum $category {
$($variant),*
})*
};
}
define_category_enum! {
Weapon { weight: f32, damage: i32, value: i32 };
Melee {
Sword (3.4, 50, 20),
Axe (5.0, 60, 25),
Hammer (6.5, 70, 30),
Dagger (1.0, 30, 15),
Spear (4.0, 55, 22),
};
Shield {
Medium (4.5, 40, 10),
Great (7.5, 80, 12),
};
Range {
Bow (2.5, 45, 18),
Crossbow (3.5, 60, 23),
Gun (5.0, 75, 28),
Scepter (2.0, 35, 16),
};
}
My goal is to access and use the tuple values (weight, damage, value) associated with each enum variant programmatically, but I'm unsure how to proceed after defining the enums using this macro. I understand how to match against enum variants, but accessing the tuple values within those variants in a generic or trait-based manner eludes me.
What you ask is possible, but not especially practical within declarative macros. This is close to (or beyond) the point where I would switch to procedural macros. The problem is that you have multiple sequences (variants, fields, ...) that don't line up one-to-one, which means you need to do this "manually", rather than rely on the macro repetition. Here is one possible solution:
macro_rules! define_category_enum {
(
$trait_name:ident { $( $field_name:ident : $field_ty:ty ),* };
$($category:ident {
$($variant:ident ( $( $val:expr ),* ) ),* $(,)?
} );* $(;)?
) => {
define_category_enum!(@define_trait($trait_name { $( $field_name : $field_ty, )* };));
define_category_enum!(@define_impls(
$trait_name { $( $field_name : $field_ty, )* };
{ $($category {
$( $variant ( $( $val, )* ), )*
}; )* }
));
};
(@define_trait(
$trait_name:ident { $( $field_name:ident : $field_ty:ty, )* };
)) => {
pub trait $trait_name {
$(fn $field_name(&self) -> $field_ty;)*
}
};
(@define_impls(
$trait_name:ident { $( $field_name:ident : $field_ty:ty, )* };
{}
)) => {
// base case: no more impls, done
};
(@define_impls(
$trait_name:ident { $( $field_name:ident : $field_ty:ty, )* };
{ $category:ident {
$( $variant:ident ( $( $val:expr, )* ), )*
}; $($rest:tt)* }
)) => {
// recursive case: emit one impl
pub enum $category { $($variant,)* }
impl $trait_name for $category { define_category_enum!(@define_fields(
{ $( $field_name : $field_ty, )* };
{ $($variant ( $( $val, )* ), )* }
)); }
// then recurse
define_category_enum!(@define_impls(
$trait_name { $( $field_name : $field_ty, )* };
{ $($rest)* }
));
};
(@define_fields(
{};
{ $($variant:ident (), )* }
)) => {
// base case: no more fields, done
};
(@define_fields(
{ $field_name:ident : $field_ty:ty, $($rest:tt)* };
{ $($variant:ident ( $val:expr, $( $rest_val:expr, )* ), )* }
)) => {
// recursive case: emit one field
fn $field_name(&self) -> $field_ty {
match self {
$(Self::$variant => $val,)*
}
}
// then recurse
define_category_enum!(@define_fields(
{ $($rest)* };
{ $($variant ( $( $rest_val, )* ), )* }
));
};
}
It is rather large, but a lot of it is boilerplate (and some parts could be shortened or made more elegant). The overall structure is this:
@
symbol as a reserved token. Technically the user could write this in their macro call, but that does not matter too much. (There are other ways to do this, using nested macros.)@define_trait
, will simply emit the trait with all its "field" functions. This is the same as your original version.@define_impls
, take care of recursively building the category enum
s and their implementations of the trait.
$($rest:tt)*
). We handle the one thing we matched, then delegate the rest to a recursive call of the macro. We need a base case (the third rule) to stop the recursion once there are no more categories.@define_fields
, invoked from the previous, emits the functions within the impl
blocks. The recursion here is similar to the previous rule, but we are simultaneously taking elements from the fields sequence and the variant's values sequence.