The most common use I’ve seen of Either is in functions that return impl Trait: If the function body produces one of two types that both implement the named trait, then Either can be used as the anonymous return type (assuming that Either implements the named trait).
The only times I've seen that have been for crate_name::Either implements crate_name:: SomeTrait - making Either a core type doesn't by itself seem to provide much value as you only save the 4 lines of the enum definition itself, and perhaps the user having to import it?
It does seem that the "ideal" core Either should be able to blanket impl "every" trait, though, which would be really nice. No idea what that looks like in actual Rust!
I think that the most common single trait I’ve seen Either used for is Iterator, where a function wants to return one of two different sequences based on a condition, and those two sequences are built through complicated adapter chains. The other implementations for std traits are similarly useful, but any individual trait implementation is fairly easy to replicate for a custom enum.
A big difference with this is that Either is binary while tuples are variadic. A variadic enum Any<...T> would a closer analog for these sorts of usecases (rather than having to nest Either<A, Either<B, Either<C, D>>>).
Just adding Either to core sidesteps basically all of the design complexities of structural sum types by virtue of being nominal and a fixed airity.
However, I think the desire here is better served by "enum impl Trait" allowing you to return multiple types implementing to the same trait as a fresh existential type (along with TAIT to be able to give the type a name).
Alternatively, this'd also be served well by some -> dyn Trait; this would compile as some -> StackBox<dyn Trait, [uAlign; SIZE]> with compiler inferred static size/align. This is very similar to -> enum impl Trait but IMHO a tiny smidgen better, as the dyn offers a clear indication of only working for object-safe traits and the dynamic dispatch involved. (In theory the optimizer could even devirtualize back to enums... but we have a track record of enum dispatch being significantly faster than dyn dispatch.)
And on that parenthetical: this usecase should also consider if it's served by the enum_dispatch crate. The technique it uses to delegate the trait implementation is brittle, but it's a good example of what can be done in userspace.
Enum::Variant is a subtype of Enum which is only the named variant, and Enum::Variant coerces to Enum; IIRC this is (experimentally?) accepted but not yet implemented. ↩︎
The inverse of the previous, using existing types as enum variants; basically sugar for newtype variants; never actually RFCd; just discussed as potential design space. ↩︎
And similarly, might could be spelled enum dyn Trait to separate it from actual unsized returns/locals ↩︎
Although to be fair, I am somewhat biased as an author of such "inline unsized type" library support and champion of the "storage API" which makes Box itself usable as StackBox↩︎
It will break if proc macro invocations get sandboxed from each other, as it uses global state to remember between invocations on the enum and the trait; it'd be much more resilient to use the technique used by ambassador for delegating trait implementations with a decl macro instrad. ↩︎
One possibility to make it easier to implement traits for Either is to allow #[derive(Either)] on traits. I know, currently traits are derived for types, not the other way around, but there's nothing preventing us from allowing that as well.
If the Either enum and the derive macro is in the standard library, that would make it very easy to implement custom traits for Either.
If we don't want to allow #[derive] on traits, a normal attribute macro, like #[derive_either], would work, too.
Though I agree with you that just having "enum impl Trait" would probably be way better and much simpler to implement. I've also used Either for Iterators, mostly because I didn't know the auto_enums crate existed.