Considering sum type nature of sum-enums, they have the following properties: [ā¦]
enum(A, B, A)
is equivalent to enum(A, B)
That is then definitely not a sum type (with all their implementation and conceptual problems). I think calling it as such is even more confusing than a "union type" (because those who would confuse them probably don't even know about union
s in Rust, which are most useful for FFI purposes). I'd prefer something like "normalized variant" or "normalized alternative" types for avoiding confusion with union
s and real sum types (enum
s).
into
is required for conversion into implicitly constructed sum-enum
[ā¦]
The main way to create sum-enums will be to use Into
trait implementation.
If we are adding a core language feature, I'd opt for making this a coercion instead, guided by type inference. This would have two advantages:
- It seems to me that you want to make the enum-like nature of these types unimportant, or treat them like so (because you mentioned that
enum(A) == A
, unlike a single-variant named enum
). This means that it is semantically redundant to have to use .into()
because the fact that there's a discriminant and enum-like behavior is basically an implementation detail at this point (especially when it's in a "return impl Trait
context). Just like you (hopefully) don't write fn foo<T>(x: T) -> T { x.into() }
, and just return x
directly.
- Automatically implementing
Into
would presumably require compiler support, either via making Into
or From
lang items, or otherwise baking them into the language. The beauty of these two conversion traits is that they don't require this. This is a pretty big conceptual change and a point of no return, so I'd be wary of committing to it.
In cases when ordering of type arguments is required it can be done based on TypeId
of each type.
Alternatively for tag we could use TypeId
directly
Doesn't TypeId
have a non-uniqueness bug? I mean, I get that it's a hash, but last time I read about it, IIRC some of the compiler/runtime developers warned that it has a way higher than negligible/acceptable chance of collision for realistically complex types. If this is the case, using it as a discriminant or an ordering key can cause unsoundness and memory unsafety bugs, because it would result in accidentally interpreting values as the wrong type.
Others have already mentioned the problematic parts of the interaction with generics (which I mostly agree with), so I won't reiterate them here.
Unresolved questions
Interaction with lifetimes. To start things off we could restrict sum-enum usage only with types which ascribe to 'static
I might be naive, but isn't the lifetime of a union of types simply the intersection of the lifetimes of its components?
impl Trait
variations: some have proposed to use enum impl Trait
IMO enum impl Trait
is just noise (actually, worse, the leaking of an implementation detail).
Syntax bikeshed: enum(T, U)
is meh at best, because that looks like a tuple. I think Centril proposed an alternative syntax in the other thread T | U
which is nicer to read, as it is evocative of semantics beacuse it denotes the "or" logical operation (corresponding to set union). It also gets rid of a pair of parenthesis and the keyword, but that's a minor point.