Idiomatic way to store "(at most) one of each variant" in an inner collection


#1

Hi, I’d like to store multiple kinds of data, keyed against an ID. Paraphased, a single ID maps to n different kinds of data. For example:

#[derive(Debug)]
struct Object {
    data: HashMap<MyId, Vec<Data>>
}

#[derive(Debug)]
enum Data {
    Foo(FooData),
    Bar(BarData),
}

However, using a Vec as the inner collection, there’s no type imposed guarantee that there could be multiple Foos or multiple Bars in there.

Is it good design to do the following, and later on iterate over data.get(&id)?.values()?

#[derive(Debug)]
struct Object {
    data: HashMap<MyId, HashMap<DataVariant, Data>>
}

#[derive(Debug)]
enum Data {
    Foo(FooData),
    Bar(BarData),
}

#[derive(Debug)]
enum DataVariant {
    Foo,
    Bar,
}

Sub question, are there different approaches between:

  • There must be one of each variant
  • There can be zero or one of each variant

#2

Why not using a HashSet / BTreeSet instead of the Vec? You have to implement Hash + Eq / Ord for your enum though.


#3

oh, to clarify, it’s not a valid case to have multiple Foos with different FooData. So a Set would have the same issue. Implementing Hash + Eq is okay


#4

Why? You can implement Eq and Hash for your enum yourself, something like.

#[derive(Debug)]
enum Data {
    Foo(FooData),
    Bar(BarData),
}

use self::Data::*; // you should avoid this in libraries or production code ;)

impl PartialEq for Data {
    fn eq(&self, other: &Self) -> bool {

        match (self, other) {
            (Foo(_), Foo(_)) | (Bar(_), Bar(_)) => true,
            _ => false,
        }
    }
}

impl Eq for Data {}

impl Hash for Data {
    fn hash<H: Hasher>(&self, state: &mut H) {
        match self {
            Foo(_) => state.write_u8(1),
            Bar(_) => state.write_u8(2),
        }
    }
}

(playground)


#5

oh I see, that works – it’s messes with my mental model of what Eq means :sweat_smile:

Thank you! :bowing_man:


#6

You can use HashMap<std::mem::Discriminant, Data>


#7

typemap offers one solution to this. It removes the need for the enums, and automatically uses the type as the key.