我使用宏在 Rust 应用程序中为不同类别的武器定义了一组枚举。每个枚举变体都与一个包含重量、损坏和价值值的元组相关联。这是宏及其用法:
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),
};
}
我的目标是以编程方式访问和使用与每个枚举变体关联的元组值(重量、损坏、值),但我不确定使用此宏定义枚举后如何继续。我了解如何匹配枚举变体,但以通用或基于特征的方式访问这些变体中的元组值却让我困惑。
您的要求是可能的,但在声明性宏中并不是特别实用。这接近(或超出)我将切换到程序宏的点。问题是您有多个序列(变体、字段……),它们不是一对一排列的,这意味着您需要“手动”执行此操作,而不是依赖宏重复。这是一种可能的解决方案:
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, )* ), )* }
));
};
}
它相当大,但很多都是样板文件(有些部分可以缩短或变得更优雅)。整体结构是这样的:
@
符号作为保留标记。从技术上讲,用户可以将其写入宏调用中,但这并不重要。 (还有其他方法可以做到这一点,使用嵌套宏。)@define_trait
,将简单地发出具有所有“字段”功能的特征。这与您的原始版本相同。@define_impls
,负责递归构建类别enum
及其特征的实现。
$($rest:tt)*
)。我们处理匹配的一件事,然后将其余的委托给宏的递归调用。一旦没有更多类别,我们需要一个基本情况(第三条规则)来停止递归。@define_fields
,从前一条规则中调用,发出 impl
块内的函数。这里的递归与前面的规则类似,但我们同时从字段序列和变体的值序列中获取元素。