Better enums

Enums in Rust are very pleasant to work with because they allow opt-out comprehensive case handling. They allow to express polychotomies which naturally occur in many situations. For me, they are perfect reification of sets in the mathematical sense; arguably, the second most important concept in mathematics after (proper) classes.

However, as reification of sets they lack some useful features [=sugar].

  1. types for enum variants

  2. adhoc trait impementations

// Will work if every type in all enum items implements MakeSound
adhoc impl MakeSound for Animal;
  1. orders on enums as sets
enum Author {
CharlesDickens,
GeorgeOrwell,
WilliamShakespeare,
GeorgeEliot,
//...
}

order Alphabetic;

order Alphabetic on Author {
CharlesDickens,
GeorgeDickens,
GeorgeEliot,
WilliamShakespeare,
//..
}

// Iter GAT should be defined if every type in all enum items implements Default
let authors_iter = Alphabetic::Iter<Author>::new();
  1. enum sum (union in the mathematical sense)
enum IOError {
// ...
}

enum ReqHandlingError {
// ...
}

// Any error in this case is either IOError or ReqHandlingError.
// Name collisions are expected to be caught by the compiler
enum Error = IOError | FormatError;

fn handle_req() -> Result<(),Error> {
  let req = recv_req().map_err(Into<Error>::into)?;
  req.handle().map_err(Into<Error>::into)?;
}

Any ideas?

5 Likes

Enum variants as different types was actually discussed a few times. 1 2 3. Currently this has a "maybe someday" status, without any specific plans. In the meantime you can crate a struct wrapped by each enum variant, or use something like the enum_variant_type crate.

Adhoc impls and orderings: I didn't understand what is it you want and what is your proposal. Note that ordering in Rust is generally managed via the Ord trait, which is easy to both derive and implement manually on enums. However, there is no way to have two orderings based on traits. That is a fundamental limitation of the trait system and it's unlikely to change in the foreseeable future.

Sum and union are very different in mathematical sense. A sum is essentially what an enum already is. It combines two types (which are possibly the same) in an ordered and uniquely distinguishable way. I.e. the sum of A and B is an enum

enum Either<A, B> {
    Left(A),
    Right(B),
}

It may be nice to have anonymous sums of types, personally I would like to have them. However, there are design issues, and it is also not something currently on the radar. E.g. see 1.

Union types, on the other hand, are something like what is implemented in Typescript. A union A | A is A itself. This is a much more contentious (though also sometimes desired) feature.

Typescript gets union types basically for free due to JS object semantics, but I imagine it is much less ergonomic to implement in Rust.

7 Likes

You might be interested in the discussions on enum impl Trait and "anonymous enums", ideas similar to some of what you're proposing.

3 Likes

How would something like adhoc impl Default for Enum work?

How about safety? An enum of bytemuck::Pod types can't be Pod due to the discriminant part of the enum not satisfying the Pod contract.

1 Like

That one is simple enough: adhoc impls can't be provided for unsafe trait. Or even just require stating unsafe again when adhocing the impl, which puts the requirements onto the developer to check that adhoc is fine for this trait.

Generally you'd restrict adhoc implementation to object safe traits, so the enum is just acting as a static form of dyn Trait for an adhoc impl.

3 Likes

Your "ad-hoc impls" are what derive macros are for.

Union types sound good in theory but they are highly non-trivial to define in the presence of generics.

I don't think general union types are being suggested in this proposal. The syntax was enum MyUnion = A | B where A and B are existing enum types and it just merges the variants from the provided enums. This syntax does not imply that A | B is a type; in fact it is slightly incompatible with A | B being a type since then this contruct would become ambiguous.

It sounds very close to something a proc-macro could do, except that I think a macro implementation would need to be provided the definitions of A and B to work.

1 Like