如何访问通过 Rust 宏定义的枚举变体中的元组值

问题描述 投票:0回答:1

我使用宏在 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),
        };
}

我的目标是以编程方式访问和使用与每个枚举变体关联的元组值(重量、损坏、值),但我不确定使用此宏定义枚举后如何继续。我了解如何匹配枚举变体,但以通用或基于特征的方式访问这些变体中的元组值却让我困惑。

问题:

  • 如何以编程方式访问每个枚举变体中包含的元组值(重量、损坏、值)?
  • 有没有办法实现这些枚举的方法(或通过特征),使我能够检索任何给定变体的这些元组值?
  • 对于构建此类数据或宏以方便访问这些内部值有什么建议吗?
rust optimization macros tuples
1个回答
0
投票

您的要求是可能的,但在声明性宏中并不是特别实用。这接近(或超出)我将切换到程序宏的点。问题是您有多个序列(变体、字段……),它们不是一对一排列的,这意味着您需要“手动”执行此操作,而不是依赖宏重复。这是一种可能的解决方案:

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
    块内的函数。这里的递归与前面的规则类似,但我们同时从字段序列和变体的值序列中获取元素。
© www.soinside.com 2019 - 2024. All rights reserved.