Ethalot
Ethalot

Reputation: 87

Is it possible to have a struct where the field is an item from an enum? Rust

Is it possible to have a generic struct such that the items in the struct are items from an enum? For example:

enum AcceptableDataType {
    String(String),
    U8(u8),
}

struct Data<K, V> 
where 
    K: AcceptableDataType,
    V: AcceptableDataType,
{
    map: HashMap<K, V>,
}

Or is this something that should be handled by traits? Im just not sure how to approach this situation in the rust way.

Upvotes: 1

Views: 1200

Answers (2)

Filipe Rodrigues
Filipe Rodrigues

Reputation: 2177

If I understand your question right, you should implement this with traits, by creating a trait that's implemented on both String and u8.

trait AcceptableDataType {}

impl AcceptableDataType for String {}

impl AcceptableDataType for u8 {}

struct Data<K, V> 
where 
    K: AcceptableDataType,
    V: AcceptableDataType,
{
    map: HashMap<K, V>,
}

Do note that in Data, you can't just "check" which type it is, you only know that it's an AcceptableDataType, which doesn't give you any info.

If you need to get back an enum of both, I'd include a method in the trait to do so, as such:

enum AcceptableDataTypeEnum {
    String(String),
    U8(u8),
}

trait AcceptableDataType {
    fn into_enum(self) -> AcceptableDataTypeEnum;
}

and also implement the function in the impls.

If you don't want any downstream users to be able to add new items to the trait, you can also make it sealed.

If you have a complicated set of impls, possibly involving generics that overlap, but you just want to use the trait as a "marker", then you can also use the unstable night-only feature marker-trait-attr.

Note however, that this prevents you from having methods on the trait, so you can't use the approach above to get back an enum, you'd need specialization for that, which is an incomplete feature.

Upvotes: 2

Chayim Friedman
Chayim Friedman

Reputation: 71210

No. There was an RFC for that, Types for enum variants, but it was postponed:

While we may want to do something along these lines eventually, given the roadmap I think there is more pressing work on the language side, and this RFC has stalled.

The idiomatic way is just to use the payload type (you may need to create a struct for it). Like:

struct Data<K, V>  {
    map: HashMap<K, V>,
}

Data<String, u8>

You may use a trait to be able to construct the enum, e.g.:

trait Enum<Variant>: Sized {
    fn from_variant(v: Variant) -> Self;
    fn into_variant(self) -> Option<Variant>;
    fn as_variant(&self) -> Option<&Variant>;
    fn as_variant_mut(&mut self) -> Option<&mut Variant>;
}

impl Enum<String> for AcceptableDataType {
    fn from_variant(v: String) -> Self { Self::String(v) }
    fn into_variant(self) -> Option<String> {
        match self {
            Self::String(v) => Some(v),
            _ => None,
        }
    }
    fn as_variant(&self) -> Option<&String> {
        match self {
            Self::String(v) => Some(v),
            _ => None,
        }
    }
    fn as_variant_mut(&mut self) -> Option<&mut String> {
        match self {
            Self::String(v) => Some(v),
            _ => None,
        }
    }
}
impl Enum<u8> for AcceptableDataType {
    fn from_variant(v: u8) -> Self { Self::U8(v) }
    fn into_variant(self) -> Option<u8> {
        match self {
            Self::U8(v) => Some(v),
            _ => None,
        }
    }
    fn as_variant(&self) -> Option<&u8> {
        match self {
            Self::U8(v) => Some(v),
            _ => None,
        }
    }
    fn as_variant_mut(&mut self) -> Option<&mut u8> {
        match self {
            Self::U8(v) => Some(v),
            _ => None,
        }
    }
}

struct Data<E, K, V>
where
    E: Enum<K>,
    E: Enum<V>,
{
    map: HashMap<K, V>,
}
// etc.

Note this trait is not perfect: it doesn't work if two variants have the same payload type. You can refine it to work in that case too, and also create a proc macro to easily derive it.

Upvotes: 1

Related Questions