Simple way to think about it is plus. Sum plus sum increases, union plus union does nothing.
1. A union type is like int | double. It means the value is one of a set of possible types. And it's a true set: `int | int | double` is indistinguishable from `int | double`.
2. A sum type is a new type built from a list of other types, which assigns a 'tag' to each possible list element, like a Rust enum. The tags are not themselves types: they are like a struct field name. It's quite possible to have a sum type with N tags, each wrapping `int`.
So with this understanding Rust has sum types but not union types, while TypeScript has union types but not sum types.
This is also why sum types are often referred to as tagged unions. They are a special case of a union, in Rust's case with support from the type system.
I've always considered both the union and tagged union to be Sum types[0].
> Two common classes of algebraic types are product types (i.e., tuples and records) and sum types (i.e., tagged or disjoint unions, coproduct types or variant types).
Edit: e.g. X | Y isn't so different from 'a' A | 'b' B
Except for the fact that the union of a type with itself (X | X) doesn't add any extra values, while a tagged union can have distinct tags with the same data type ('a' A | 'b' A) and actually sums the possible values for each tag. They are equivalent only so long as the unions are disjoint with no overlap between the members.
Rust also has 'union' types [1] that work similar to the union types in C, C++.
Recently there has also been a lot of discussion [2] about possibly adding anonymous enums to the language, and whether these should have sum or union semantics.
WRT to 2, one useful property is that you can have two or more labels for the anonymous type `()`, such as `enum MyBool { True, False }` or `enum JSON { List(Array<JSON>), Object(List<(String, JSON)>), Number(f64), String(String), True, False, Null }`.
A sum type is a tagged union [1]. In C, a sum type can be modeled by the combination of an enum and a union. The enum would tell you which field of the union you should use. In Rust, enum's subsume unions and enums of C. This is analogous to a a disjoint union [2] in set theory. Notice that in both cases you have a pair of data. Sum types and disjoint unions are both instances of coproducts in category theory, by the way.
According to the tagged union Wikipedia you referenced, a disjoint union is also considered a tagged union. Whether or not that's actually a correct description within the vernacular of computer science, I don't know.
"In computer science, a tagged union, also called a variant, variant record, choice type, discriminated union, disjoint union, sum type or coproduct, ..."
That's right. It's unfortunate that Rust calls them "enums", since as you noted that term already had a well-established meaning in other languages, and the concept that Rust calls "enums" also had several existing names (sum type, coproduct type, disjoint union, and, more generally, algebraic data type) in the literature and in prior languages.
Language designers: stop changing the meanings of words! I know you mean well and you're trying to tie unfamiliar ideas to familiar ones for beginners, but you end up causing more confusion for everyone in the long run.
When you say "Rust enums are also enums", that's not true in general. What is true is that _some_ Rust "enums" (like your `Colour` example) can be represented as traditional enums. You don't prove a universal quantification with a single example.
Rust calls them enum to be familiar to people coming from C-family languages, which is a large target.
> since as you noted that term already had a well-established meaning in other languages
Rust enums "degenerate" to a C-style enum (except typesafe) as it can be repr'd to a number and it's possible to select the discriminant (if there's no associated data).
> the concept that Rust calls "enums" also had several existing names (sum type, coproduct type, disjoint union, and, more generally, algebraic data type) in the literature and in prior languages.
Pretty much none of which are actually part of the language e.g. in Haskell or OCaml the designator is `type`, and it's used for both sum types and product types: the sum type simply has a single constructor.
But Rust doesn't use `type`, it uses `struct`. And a `struct` with multiple variants doesn't make sense.
Enum has one meaning in C family languages. Other languages may have different meanings. For example, in Haskell something is an enum if there is an invertible mapping between that type and (a contiguous subset of) the machine integers (with complications).
In other languages the concept might correspond to (finite) recursively enumerable sets (ie you can list all their elements).
Union types are basically an anonymous form of sum types. In the same way tuples and structs / records are both forms of product types.
Statically typed languages which support sum types generally only support the named version (because it tends to be more useful and powerful).
> I guess enums are a type of sum type, but seems this is more specifically about enums
Depends on the language, C's enums are not types at all, Java's or C++'s are product types. Technically enums (or more generally tagged unions, which is what Rust's enums are) is a superset of sum types as you can have multiple variants of the same type, but that's not leveraged here.
> Union types are basically an anonymous form of sum types.
This is false on multiple levels. First, being a sum type has nothing to do with the type being named vs. anonymous. What makes a sum type a sum type is that it's a categorical coproduct, whether you give it a name or not. Second, sum types are synonymous with _disjoint_ (i.e., tagged) unions, not unions. Consider the union of boolean with itself. The result would be equivalent to boolean, because union is an idempotent operation. Disjoint union, or sum, would give you a type with 4 values instead of 2.
> Technically enums (or more generally tagged unions, which is what Rust's enums are) is a superset of sum types as you can have multiple variants of the same type
That just how sum types work (have you ever wondered why they are called "sum" in the first place?). You're thinking of union types.
Rust enums are full union types. i.e. while each possibility in a (say) Java enum must be of the same type, each possibility in a Rust enum can be of a different type.
However, Rust's enum-type members are still named - to my knowledge you can't have anonymous enum type members in Rust.
In the example you gave, the type-of `left` is still `SumType::Left` instead of being just `String`.
I'm not too familiar with Rust to say, but I don't consider this to be a syntactically zero-cost abstraction (even if the wrapper-types are elided by the compiler) because we still have more keyboard typing to do than we should be doing, imo.
Ah, that's to enable you to have multiple members that could hold `String` values. Yeah, it comes with the (syntactic) cost that you have to identify the member.
This is just a difference between named and anonymous types, really. This is very similar to named functions vs lambda functions, both provide the same exact functionality but avoiding superfluous identifiers makes programming more ergonomic.
More specifically sum types in Rust must also be tagged, meaning you need to explicitly construct and deconstruct them. This aspect is along the type-alias vs newtype axis which gets into structural vs nominative typing and the trade-offs therein.
So yes, Rust enums and Typescript union types are both exactly sum-types and the differences are due to the surrounding decisions made about the languages. Rust's sum types are named and tagged, Typescript's are anonymous and untagged, but they're both sum types.
> So yes, Rust enums and Typescript union types are both exactly sum-types…
As others have pointed out, untagged unions are not sum types because the union of a type with itself has no effect, whereas adding a type to itself yields twice as many possible values. Untagged unions can function as sum types when there is no overlap between the members, but not in the general case.
To illustrate the difference: You can construct every possible algebraic type as some combination of void (no values), unit (one value), sum (|A + B| = |A| + |B|), and product (|A * B| = |A| * |B|). This does not work if the sum type is replaced with an untagged union. You can't even get as far as constructing the equivalent of the boolean type; while |Unit + Unit| has two distinct values, |Unit ⋃ Unit| only has one value.
I was expecting union types (e.g., https://www.typescriptlang.org/docs/handbook/unions-and-inte... or https://dotty.epfl.ch/docs/reference/new-types/union-types.h... )
I guess enums are a type of sum type, but seems this is more specifically about enums