Earlier quoted context omitted.
Ah ok I think we're mixing up terms here - in the context of systems programming languages, a "tagged" union refers to an integer in front of a bag of bytes that holds the data of the "un tagged" union. Rust has both tagged (enums) and untagged (unions) union types. What you're asking about is a discriminated vs non-discriminated union, and indeed, that's exactly what I'm talking about. A | B |C is not the same type…
> because Either > cannot type check as Either Why would you want the former to type check as the latter? Where do you see the complexity?
fn foo () -> A | B | C {
if condition {
bar();
} else {
baz();
}
}
fn bar() -> A | B {
...
}
fn baz() -> B | C {
...
}
vs fn foo () -> Either {
if condition {
match bar() {
Either::Left(a) => Either::Left(a),
Either::Right(b) => Either::Right(Either::Left(b)),
}
} else {
match baz() {
Either::Left(b) => Either::Right(Either::Left(b),
Either::Right(c) => Either::Right(Either::Right(b)),
}
}
}
fn bar() -> Either {
...
}
fn baz() -> Either {
...
}
The latter code composes poorly and requires an extra branch at runtime. It is fundamentally more complex to dispatch on nested discriminated unions instead of flat non-discriminated unions both for the programmer to write, read, and for the runtime to execute.The compiler can also optimize the representation of the anonymous enum based on the context in which its created, whereas its more difficult to do that in the discriminated case.
This isn't a controversial opinion, there are mountains of Typescript written in this style.