Enum subsets with zero-cost conversions to supersets

Are there any libraries that help make the following zero-cost (in the Into case) and not require a clone (for TryInto)? Presumably this could be achieved with a procedural macro on the super enum definition.

enum Superset { A, B }
// I want a guarantee that the discriminant in Subset exactly matches
// the matching discriminants in Superset, i.e. Subset::A has the same
// bytes as Superset::A.
enum Subset { A }

let sub = Subset::A;

// this should be zero cost (i.e. can just be a `std::mem::transmute`,
// if I understand `transmute` correctly)
let super: Superset = sub.into();

// This should not require a clone, just recursing into the type to ensure
// that every variant in the input type has the correct variant. If it matches,
// it should (effectively) just be an `Ok(std::mem::transmute(input))`
let a: Result<Subset, _> = b.try_into();

Thank you! I'm a bit bummed that doing this manually in the naive way basically requires a clone.

(On the off chance that there is someone reading who wants to implement this, my use case involves a Superset that has many overlapping subsets; some subsets are subsets of other subsets, as well. You should be able to convert from arbitrary subsets with try_into, and from any subset to a containing enum with zero cost.)

          /      \
   InputTypes   OutputTypes
          \      /
         ScalarTypes (valid as both input and output types)

Not quite, representations of enums aren't guaranteed so this'd be UB as written.

I'm really not sure what you're trying to express here, but if your type is as simple as is written, you can implement/derive Copy for it to avoid the clone which usually signals an expensive clone.

In these cases, you have to trust the compiler to optimize to the right thing. You need to write code to do the translation yourself (or get a crate to auto-generate it but that's probably more of a headache than is necessary).

In any case, when your enums have data in a variant, you generally cannot transmute them -- their layout is unspecified (and the compiler may niche optimize some types).

Thanks for the response!

I'm not really sure

Oops, sorry; I changed the variable names in some places, but not the others. What I meant to convey with the try_into example is let subset_result: Result<Subset, _> = superset.try_into(). This must return a result, since the superset's variant could not be part of the Subset type.

In practice, what I want is for this to work for container types, e.g. Vec<Subset> to be convertible to a Vec<Subset> without clones. .into_iter().map(zero_cost_transform).collect() is a lot of work for something that should be doable as a transmute!

This'd be UB as written

Yes, I think this would require some sort of type hackery along the lines of:

enum Superset {
// which emits some magical code like
#[discriminant_bits(2)] // Pretty sure this doesn't exist
enum Superset {
  #[discriminant(0)] // Nor does this

enum Subset {
impl Subset { ... }
impl Superset { ... }

Anyway, one can dream!

If you're just talking fieldless enums, you could give them a repr(u16) or whatever. That would allow for an in-place transformation so long as you ensure the discriminant values match across all enums. (You can specify the discriminant values.)

When the enums have fields, they could be different sizes or alignments. If you want to transmute those, you'll have to ensure they all share the same size and alignment. You'll still have have to use a well-defined layout like repr(u16) or repr(C, u16) as well, and naturally every variant should have the same data in the same order in addition to the same discriminant value. Here's the best documentation I know of on enum layouts.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.