首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >是否有从枚举自动创建字典的宏?

是否有从枚举自动创建字典的宏?
EN

Stack Overflow用户
提问于 2021-12-22 08:21:51
回答 2查看 380关注 0票数 2

枚举显然是一种键/值对结构。因此,最好自动创建一个字典,其中枚举变体成为可能的键,它们的有效载荷是关联的值。没有有效载荷的键将使用单位值。下面是一个可能的用法示例:

代码语言:javascript
运行
复制
enum PaperType {
    PageSize(f32, f32),
    Color(String),
    Weight(f32),
    IsGlossy,
}

let mut dict = make_enum_dictionary!(
    PaperType, 
    allow_duplicates = true,
);

dict.insert(dict.PageSize, (8.5, 11.0));
dict.insert(dict.IsGlossy, ());
dict.insert_def(dict.IsGlossy);
dict.remove_all(dict.PageSize);

值得注意的是,由于枚举仅仅是可选地携带有效负载的值列表,因此自动神奇地从它构造字典会出现一些语义问题。

  1. 强类型Dictionary<K, V>如何维护enum所固有的区分/值类型依赖关系,其中每个判别器都有特定的有效负载类型? K1(V1),K2(V2),…,Kn(Vn),}
  2. 如何方便地在没有有效载荷的代码中引用enum鉴别(Ta.K1)?它是什么类型的(Ta::Discriminant?)?
  3. 是要设置值并获取整个enum值,还是只获取有效载荷? 获取(& self,key: Ta::判别式) ->选项集(&mut,value: Ta)

如果有可能用其变体的另一个enum自动增强现有的enum,那么在下面的伪代码中,一个合理有效的解决方案似乎是可行的:

代码语言:javascript
运行
复制
type D = add_discriminant_keys!( T );

impl<D> for Vec<D> {
    fn get(&self, key: D::Discriminant) -> Option<D> { todo!() }
    fn set(&mut self, value: D) { todo!() }
}

我不知道宏add_discriminant_keys!或构造D::Discriminant是否可行。不幸的是,尽管有这样的建议,我仍然在锈蚀池的浅浅处溅起水花。然而,其宏观语言的大胆性表明,对于那些相信它的人来说,许多事情是可能的。

处理重复项是实现细节。

Enum鉴别通常是函数,因此具有固定的指针值(据我所知)。如果这些值可以成为枚举中关联类型的常量(比如一个特征),并且具有与strum::EnumDiscriminants实现的属性类似的属性,那么事情就会看起来很好。实际上,EnumDiscriminants似乎是一个足够的临时解决方案。

基于HashMap的通用实现使用strum_macros板条箱是基于铁锈操场提供的;但是,由于铁锈操场无法从那里加载strum板条箱,因此在那里没有功能。一个宏导出的解决方案会很好。

EN

Stack Overflow用户

发布于 2021-12-23 01:23:13

首先,就像这里已经说过的,正确的方法是带有可选值的结构。

但是,为了完整起见,我将在这里向您展示如何使用proc宏来实现这一点。

当您想要设计一个宏,特别是一个复杂的宏时,首先要做的是计划发出的代码是什么。因此,让我们尝试为以下简化枚举编写宏的输出:

代码语言:javascript
运行
复制
enum PaperType {
    PageSize(f32, f32),
    IsGlossy,
}

我已经警告过您,我们的宏不支持大括号样式的枚举变体,也不支持组合枚举(您的add_discriminant_keys!())。两者都有可能得到支持,但两者都会使这个已经复杂的答案变得更加复杂。我很快就会提到他们。

首先,让我们设计一下地图。它将放在一个支撑箱里。让我们调用这个机箱denum (稍后需要一个名称,当我们从宏中引用它时):

代码语言:javascript
运行
复制
pub struct Map<E> {
    map: std::collections::HashMap<E, E>, // You can use any map implementation you want.
}

我们希望将鉴别作为密钥存储,而将枚举存储为值。因此,我们需要一种方法来参考自由判别。所以,让我们创建一个特性Enum

代码语言:javascript
运行
复制
pub trait Enum {
    type DiscriminantsEnum: Eq + Hash; // The constraints are those of `HashMap`.
}

现在我们的地图会是这样的:

代码语言:javascript
运行
复制
pub struct Map<E: Enum> {
    map: std::collections::HashMap<E::DiscriminantsEnum, E>,
}

我们的宏将生成Enum的实现。手写的,如下所示(注意,在宏中,我将它包装在const _: () = { ... }中。这是一种防止名称污染全局名称空间的技术):

代码语言:javascript
运行
复制
#[derive(PartialEq, Eq, Hash)]
pub enum PaperTypeDiscriminantsEnum {
    PageSize,
    IsGlossy,
}

impl Enum for PaperType {
    type DiscriminantsEnum = PaperTypeDiscriminantsEnum;
}

下一首。insert()操作:

代码语言:javascript
运行
复制
impl<E: Enum> Map<E> {
    pub fn insert(discriminant: E::DiscriminantsEnum, value: /* What's here? */) {}
}

在当前锈蚀中,没有办法将枚举判别式作为一种独特的类型来引用。但是有一种方法可以将struct称为一种独特的类型。

我们可以考虑以下几点:

代码语言:javascript
运行
复制
pub struct PageSize;

但这会污染全局名称空间。当然,我们可以称它为PaperTypePageSize,但我更喜欢类似PaperTypeDiscriminants::PageSize的东西。

救援队!

代码语言:javascript
运行
复制
#[allow(non_snake_case)]
pub mod PaperTypeDiscriminants {
    #[derive(Clone, Copy)]
    pub struct PageSize;
    #[derive(Clone, Copy)]
    pub struct IsGlossy;
}

现在,我们需要在insert()中使用一种方法来验证所提供的鉴别确实与所需的枚举匹配,并引用它的值。一个新的特点!

代码语言:javascript
运行
复制
pub trait EnumDiscriminant: Copy {
    type Enum: Enum;
    type Value;
    
    fn to_discriminants_enum(self) -> <Self::Enum as Enum>::DiscriminantsEnum;
    fn to_enum(self, value: Self::Value) -> Self::Enum;
}

下面是我们的宏将如何实现它:

代码语言:javascript
运行
复制
impl EnumDiscriminant for PaperTypeDiscriminants::PageSize {
    type Enum = PaperType;
    type Value = (f32, f32);
    
    fn to_discriminants_enum(self) -> PaperTypeDiscriminantsEnum { PaperTypeDiscriminantsEnum::PageSize }
    fn to_enum(self, (v0, v1): Self::Value) -> Self::Enum { Self::Enum::PageSize(v0, v1) }
}
impl EnumDiscriminant for PaperTypeDiscriminants::IsGlossy {
    type Enum = PaperType;
    type Value = ();
    
    fn to_discriminants_enum(self) -> PaperTypeDiscriminantsEnum { PaperTypeDiscriminantsEnum::IsGlossy }
    fn to_enum(self, (): Self::Value) -> Self::Enum { Self::Enum::IsGlossy }
}

现在是insert()

代码语言:javascript
运行
复制
pub fn insert<D>(&mut self, discriminant: D, value: D::Value)
where
    D: EnumDiscriminant<Enum = E>,
{
    self.map.insert(
        discriminant.to_discriminants_enum(),
        discriminant.to_enum(value),
    );
}

和琐碎的insert_def()

代码语言:javascript
运行
复制
pub fn insert_def<D>(&mut self, discriminant: D)
where
    D: EnumDiscriminant<Enum = E, Value = ()>,
{
    self.insert(discriminant, ());
}

get() (注意:通过向特征EnumDiscriminant添加一个带有签名fn enum_to_value(enum_: Self::Enum) -> Self::Value的方法,可以在移除时单独获得值。它可以是unsafe fn并使用unreachable_unchecked()来获得更好的性能。但是有了get()get_mut(),返回引用就更困难了,因为您无法获得对判别值的引用。尽管如此,这里有个游乐场还是这样做的,但需要每晚这样做):

代码语言:javascript
运行
复制
pub fn get_entry<D>(&self, discriminant: D) -> Option<&E>
where
    D: EnumDiscriminant<Enum = E>,
{
    self.map.get(&discriminant.to_discriminants_enum())
}

get_mut()非常类似。

请注意,我的代码不处理重复项,而是覆盖它们,因为它使用HashMap。但是,您可以轻松地创建自己的地图,以另一种方式处理重复的映射。

现在我们已经清楚地了解了宏应该生成的内容,让我们来编写它!

我决定把它写成派生宏。您也可以使用属性宏,甚至可以使用类似于函数的宏,但是必须在枚举的声明站点调用它,因为宏不能检查应用到的代码以外的代码。

这个枚举看起来就像:

代码语言:javascript
运行
复制
#[derive(denum::Enum)]
enum PaperType {
    PageSize(f32, f32),
    Color(String),
    Weight(f32),
    IsGlossy,
}

通常,当我的宏需要助手代码时,我会将这些代码放在crate中,宏放在crate_macros中,然后从crate中重新导出这个宏。因此,denum中的代码(除了前面提到的EnumEnumDiscriminantMap):

代码语言:javascript
运行
复制
pub use denum_macros::Enum;

denum_macros/src/lib.rs:

代码语言:javascript
运行
复制
use proc_macro::TokenStream;

use quote::{format_ident, quote};

#[proc_macro_derive(Enum)]
pub fn derive_enum(item: TokenStream) -> TokenStream {
    let item = syn::parse_macro_input!(item as syn::DeriveInput);
    if item.generics.params.len() != 0 {
        return syn::Error::new_spanned(
            item.generics,
            "`denum::Enum` does not work with generics currently",
        )
        .into_compile_error()
        .into();
    }
    if item.generics.where_clause.is_some() {
        return syn::Error::new_spanned(
            item.generics.where_clause,
            "`denum::Enum` does not work with `where` clauses currently",
        )
        .into_compile_error()
        .into();
    }

    let (vis, name, variants) = match item {
        syn::DeriveInput {
            vis,
            ident,
            data: syn::Data::Enum(syn::DataEnum { variants, .. }),
            ..
        } => (vis, ident, variants),
        _ => {
            return syn::Error::new_spanned(item, "`denum::Enum` works only with enums")
                .into_compile_error()
                .into()
        }
    };

    let discriminants_mod_name = format_ident!("{}Discriminants", name);
    let discriminants_enum_name = format_ident!("{}DiscriminantsEnum", name);

    let mut discriminants_enum = Vec::new();
    let mut discriminant_structs = Vec::new();
    let mut enum_discriminant_impls = Vec::new();
    for variant in variants {
        let variant_name = variant.ident;

        discriminant_structs.push(quote! {
            #[derive(Clone, Copy)]
            pub struct #variant_name;
        });

        let fields = match variant.fields {
            syn::Fields::Named(_) => {
                return syn::Error::new_spanned(
                    variant.fields,
                    "`denum::Enum` does not work with brace-style variants currently",
                )
                .into_compile_error()
                .into()
            }
            syn::Fields::Unnamed(fields) => Some(fields.unnamed),
            syn::Fields::Unit => None,
        };
        let value_destructuring = fields
            .iter()
            .flatten()
            .enumerate()
            .map(|(index, _)| format_ident!("v{}", index));
        let value_destructuring = quote!((#(#value_destructuring,)*));
        let value_builder = if fields.is_some() {
            value_destructuring.clone()
        } else {
            quote!()
        };
        let value_type = fields.into_iter().flatten().map(|field| field.ty);
        enum_discriminant_impls.push(quote! {
            impl ::denum::EnumDiscriminant for #discriminants_mod_name::#variant_name {
                type Enum = #name;
                type Value = (#(#value_type,)*);

                fn to_discriminants_enum(self) -> #discriminants_enum_name { #discriminants_enum_name::#variant_name }
                fn to_enum(self, #value_destructuring: Self::Value) -> Self::Enum { Self::Enum::#variant_name #value_builder }
            }
        });

        discriminants_enum.push(variant_name);
    }

    quote! {
        #[allow(non_snake_case)]
        #vis mod #discriminants_mod_name { #(#discriminant_structs)* }

        const _: () = {
            #[derive(PartialEq, Eq, Hash)]
            pub enum #discriminants_enum_name { #(#discriminants_enum,)* }

            impl ::denum::Enum for #name {
                type DiscriminantsEnum = #discriminants_enum_name;
            }

            #(#enum_discriminant_impls)*
        };
    }
    .into()
}

这个宏有几个缺陷:例如,它没有正确处理可见性修饰符和属性。但在一般情况下,它有效,你可以微调它更多。

如果您也想支持大括号样式的变体,您可以用数据创建一个结构(而不是我们目前使用的元组)。

如果不使用派生宏,而是使用类似于函数的宏,并在两个枚举上调用它,例如:

代码语言:javascript
运行
复制
denum::enums! {
    enum A { ... }
    enum B { ... }
}

然后,宏必须组合这些判别符,并在使用映射操作时使用类似于Either<A, B>的内容。

票数 2
EN
查看全部 2 条回答
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/70446082

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档