Are you replying to my original post, or @GolDDranks’s tag-on suggestion? It’s not immediately clear.
Pre-RFC: Using existing structs and tuple-structs as enum variants
Actually, generally to both proposals
. I’m just suggesting that whatever the goals, it should first be clear there’s a large-enough need for that to warrant the feature and that it gets created with as minimal cost as possible.
Probably worth linking to https://github.com/rust-lang/rfcs/pull/2363, in which the ability to set discriminant values on any enum (non just C-like enums) is proposed with the explicit motivation of making it safe to assume that a trivial transmute/pointer cast is a sound implementation of conversion from an enum to a subset enum. Apparently Servo badly needs that.
I’m guessing that guaranteeing a transmute-only From impl for the subset-to-superset conversion ought to be a requirement for any “enum subset” feature worth adding to the core language. I’m also guessing a proc macro cannot provide that since proving the transmute valid means proving the compiler won’t apply different layout optimizations to the subset and the superset enums. Hopefully there’s a real bit-twiddler here who can confirm that.
Fair enough. The syntax of my suggestion isn’t really adding anything new though: it just changes where it can be put. I find it pretty intuitive. 
A slightly different version of the idea: What about anonymous enums?
So you’d have the type enum(Foo|Bar|Baz), say, which could be matched with patterns like match y { x: Foo => ..., x: Bar => .... And it would magically implement any trait implemented by all of its variants (other than things like Default, I guess).
That would enable things like type IpAddrAlt = enum(Ipv4Addr | Ipv6Addr);, or a function that’s -> Result<i32, enum(NegativeInputError|OverflowError)>.
Obviously this couldn’t let you do things like type Foo<T, U> = enum(T|U);, but my intuition is that the rules needed for that are the same as the overlap rules for trait impls – that example doesn’t work for the same reason that impl<T> Foo for T and impl<U> Foo forU cannot both exist.
I am not a huge fan of the ascription-style syntax, since it sort of suggests that it’s a general phenomenon (for example, the syntax you suggest is how Scala gives access to Java’s instanceof). I think that, for symmetry with tuples, we should have a “desugar” of
type Foo = Bar | Baz; // the `enum` bit is unnecessary given current grammar
// into
enum Foo {
0(Bar), 1(Baz)
}
match foo {
0(x) => ..
1(y) => ..
}
As far as traits, I think they should be implemented manually (like Clone et. al. are in core for tuples) by macro (and eventually variadic generics). To take the analogy with structs further, I imagine “sum enums” (if we call these sum types) in analogy to “tuple structs”:
enum Foo(Bar | Baz); // generates the same enum with the same digit variants.
let x = Foo::0(Bar);
This syntax also allows us to write Foo | Foo as a valid type.
I’m not a fan because that means I need to remember whether it’s Bar | Baz or Baz | Bar, for which my memory is not good enough. I think it’s an important feature of enums and (non-tuple) structs that you can reorder their variants whenever you want without* affecting functionality.
* Well, unless you’re using #[derive(Ord)], but I consider that an anti-pattern on things that aren’t tuple structs.
Imagine this table:
| product type | coproduct type
named fields | struct | enum
unnamed fields | tuple struct | sum enum
anonymous | tuple | sum
The intent here is to make enums truly dual to structs. Two of these spaces are missing, which could be filled in by anonymized versions of enums. (I could imagine that a more obnoxious name for “sum” is “cotuple”.)
I disagree with having not to not care if it’s Bar | Baz or Baz | Bar. After all, (Bar, Baz) and (Baz, Bar) are different types! I’d also like to be able to say Bar |, since (Bar,) is a valid tuple, though I question your
desire to use it. I also especially dislike that A | B implicitly has where A != B, which is both unecessarily restrictive and unpronounceable, so we can’t use A | B as an unbiased version of Result.
After all, the equivalent objects in math, coproducts A |_| B, are not commutative in any mainstream setting, and being able to write A |_| A turns out to be a useful edge case.
I think the more important part is that (Bar,) and (Bar, Bar,) and (Bar, Bar, Bar,) are different types.
I would be quite happy for enum(A | B | A) to be the same type as enum(A | B), and even with type AB = enum(A|B); type BC=enum(B|C); type ABC = enum(AB|BC); for ABC be the same type as enum(C|B|A).
Yeah, I think we just fundamentally disagree about this sort of collapse behavior. I see that sort of coproduct collapse as a nice idea on paper but one that will ultimately produce more problems than it will solve, such as the fact that the following function can’t be written:
fn first<T: Default, U>(x: T | U) -> T {
match x {
// using your syntax
x: T => x,
_: U => T::default(),
}
}
If T::default() has side-effects, the behavior of first::<T, T>(t) is now undefined! So in your proposal, either such generic functions are unacceptable (which means the compiler needs to check for them…) or we need to add where T != U. Worse, without this where clause you can’t use generic sums in structs! Hell, using T | () as an ad-hoc Option (why the hell would you, I know, but you get my point) is no longer allowed!
My syntax side-steps this problem, since you need to call first as first::<T, T>(0(t)). However, I think that in a non-generic context I think it would be fine to be able to write first(t) if it was fn foo(x: i32 | &str).
@scottmcm @drXor Interesting suggestions. Personally, I’d like to see the two absent anonymised versions of enums (as @drXor called them) implemented, but in addition to my original proposal. I see them as complementary rather than alternatives. If we get enum-variants-as-types too, then all the better!
As for the debate on commutativity and idempotency of enum(...) as a type constructor, I’m tempted by the mathematical reasoning, but equally, how would one match on a type like enum(A | A) or enum(A | B | A)?
With type ascription in patterns appearing plausibly-going-to-happen, I more than ever think that this is the right way for this to be consumed:
match x {
y: Ipv4Addr => ...,
z: Ipv6Addr => ...,
}
I don’t know the rest of the design, though 
Perhaps the answer is that if the types are the same, it runs the first matching arm – like it would if you translated it into a sequence of downcasts off an any. And yes, that means you can’t use it place of Result, but I think that’s fine in same way you can’t tell the difference between (r, g, b) and (x, y, z) in tuples the way you can between { r, g, b } and { x, y, z } in structs.
If we go with the ordered-variants approach, probably pattern matching just based on order would work best, i.e.:
match x {
var1 => ...,
var2 => ...,
var3 => ...,
}
I feel this parallels the philosophy of tuples and tuple structs the best.
In the specific example from Syn each variant shares a field named attrs, so it might be simpler to allow the user to derive a method named get_mut_attrs that returns a mutable reference to a version the shared field. (Note: I haven’t tested if mem::replace actually works with a reference here)
match self.get_mut_attrs() {
Some(attrs) => mem::replace(*attrs, new),
None => Vec::new()
}
I feel like this would be extremely surprising if you didn’t know that x was such a type, as it looks identical to obviously-dead code. And it gets really strange if you only want the third one – does that need this?
match x {
_ => {}
_ => {}
var3 => ...,
}
Compare the type ascription one, where something like
if let addr: Ipv6Addr = x {
feels completely natural. (Can you even do an order-based thing in if let?)
My 2 cents: | shouldn’t be used unless there’s either collapsing behavior (T | T = T), or enforced disjointness (in general, T | U is disallowed, but something like Option<T> = Some<T> | None<T> is allowed).
I personally don’t like anonymous sum types. I think that if you really need to be able to do different things for each of the possible cases, you should name the constructors.
That sort of goes hand in hand with what I do want T | U for: automatically generated trait impls.
Even if you can’t use pattern-matching in the general case where the types are unknown, that’s not that significant of a limitation if instead you have access to trait methods, for traits that both T and U implement.
With -> impl Trait, the compiler could be generating T | U types when the types it sees differ, and then the caller of that function will still be able to use Trait, without anyone writing a match on T | U.
Also, collapsing behavior is great for this, if the types end up the same, you don’t waste space!
I can’t parse this.
Yep, I’ve always been in favour of this! This was discussed before in a long issue (RFC PR even?), but I can’t remember where. A few people claimed it was too much compiler magic, but I’m not bothered. The convenience outweights that.
I agree it has that benefit along with some other conveniences, but the mathematical/type theoretic argument (along with consistency with enum) is still a strong one.
Okay, fair point. That syntax is fine with me, but I still think we need to support either
match x {
y: Ipv4Addr => ...,
z: Ipv6Addr => ...,
}
or
match x {
Ipv4Addr @ y => ...,
Ipv6Addr @ z => ...,
}
as the primary pattern-matching syntax for unnamed enums. (The second is a little more in line with existing syntax I feel, but either could work.)
I absolutely agree that
match x {
y @ Ipv4Addr { .. } => ...,
z @ Ipv6Addr { .. } => ...,
}
should work, since each of those arms individually already works today.
And with unit structs – such as error ones – I’d expect just this to work too:
match x {
OverflowError => ...,
NotADigitError => ...,
}
the same way that let OverflowError = x; works for unit structs.