Enum hierarchy to hash map

What I'm trying to do is a little bit weird. Initially I used a struct:

struct A {
  entity: String,
  entity_id_type: String,
  entity_id_source: String,
}

I wanted to make sure, that it's not possible to create wrong combinations of these three values, so I changed it to an enum hierarchy:

enum Entity {
  EntityA(EntityAIdType)
}

enum EntityAIdType {
  TypeA(TypeASource)
}

enum TypeASource {
  SourceX,
  SourceY,
  SourceZ
}

Later on I need to write a lot of Entities into a file and it is a user preference which of the three fields (as seen in the struct) can be printed to the file.

Is it possible to achieve something like this without a massive match for all possible combinations? I would be fine with output as a list or hash map and can filter it while writing to a file. I would love to have enums to not allow wrong state in the application.

Any ideas?

You could enforce this through validation.

Maybe use a serialization library like serde or bincode?

Not really an option, the format is custom and I don't want to spend time writing a serde serializer for it. It needs to be human-readable as well.

That's what the enum is for, to prevent invalid state. Validation on the struct will result in a huge match as well.

Since using the enums makes it impossible to create wrong combinations, I'm not understanding why you need the big match. Is this related to the first paragraph where you talk about writing to the file and the user preference? It may help to give an example of what you need to do.

Yes, writing the selected combination into the file is a problem now with the enum.

Let's assume there is no possibility to select what you want to write and it always needs to write all the information. The input is a Vector of Entities. And let's assume we just write CSV lines to stdout.

With the struct it was just a matter of writing all its fields, separated by comma. I always had all the fields, so it was super easy:

for entry in all_entries {
    print!(format!("{},{},{}\n", &entry.entity, &entry.entity_id_type, &entry.entity_id_source));
}

How can I do the same with the enum now? It's not strings anymore, just enums all the way. It's always a hierarchy of 3: Entity::EntityA(EntityAIdType::TypeA(TypeASource::SourceX)) or Entity::EntityP(EntityPIdType::SomeOtherIdType(SomeOtherIdTypeSource::InterestingSource)).

match entity {
    Entity::EntityA(EntityAIdType::TypeA(source) => print!(format!("{},{},{}\n", "EntityA", "TypeA", &source))
    Entity::EntityP(EntityPIdType::SomeOtherIdType(source)) => print!(format!("{},{},{}\n", "EntityP", "SomeOtherIdType", &source))
}

is my current solution, but it will get out of hand pretty quickly. I have 5 entities, each with at least 3-4 types and each type has 1-5 sources. match with ~70 branches doesn't sound like fun.

Ideally I wouldn't write to stdout and I would get Vectors of length = 3 or HashMaps from the match, with the 3 levels of hierarchy and write only the selected ones to the file, but that's just special case of the example with stdout.

When you delegate to the nested level you need more matches but each match has less arms

let (entity, (e_id_type, source)) = match entity {
    Entity::EntityA(a) => ("EntityA", a.id_and_source()),
    Entity::EntityP(p) => ("EntityP", p.id_and_source()),
};

You can also try to use the enum_dispatch trait to autogenerate some of the code.

1 Like

Could you impl Display for each type?

Then outer types can just forward to the display of inner types.

Still need to match once for each type, but no explosion of combinations.

2 Likes

Thanks for the suggestion, that's a great workaround for now!

If I ever learn macros, then it'll be a breeze to implement this for each enum.

1 Like