meetings/2025/LDM-2025-09-29.md
Champion issue: https://github.com/dotnet/csharplang/issues/9662
Specification: https://github.com/dotnet/csharplang/blob/38fd5f33d285cb190268f98cea16223cc0a5b8bc/proposals/unions.md
Proposals:
Today we are following up from last time. We've decided that we want to have syntax outside of the body of the union for the list of allowable cases, and today we need to decide what the initial form of that will be. This is by no means a truly final decision, and part of the reason for front-loading this decision is so that we can get previews into user hands for feedback. We gathered a number of possible syntaxes here, though we did not discuss the merits of every single one.
union Pet(Cat, Dog, Bird);
union Pet[Cat, Dog, Bird];
union Pet(Cat | Dog | Bird);
union Pet(Cat or Dog or Bird);
union Pet allows Cat, Dog, Bird;
union Pet includes Cat, Dog, Bird;
union Pet contains Cat, Dog, Bird;
union Pet has Cat, Dog, Bird;
union Pet with Cat, Dog, Bird;
union Pet of Cat, Dog, Bird;
union Pet switch Cat, Dog, Bird;
union Pet case Cat, Dog, Bird;
union Pet is Cat, Dog, Bird;
union Pet is Cat or Dog or Bird;
Broadly speaking, our ideas fall into two categories: a list surrounded by some kind of delimiter, such as () or [], and a list started by some keyword, more
similar to generic constraint clauses. When we considered these, we also wanted to make sure we thought about them in the broader context of what a union type
will be able to do: have generic type parameters, constraints, implement interfaces, have a body with declarations, etc. Below are some assorted notes from
discussions of a few of these syntaxes:
union Pet(Cat, Dog, Bird) looks like primary constructor syntax at first glance, will it conflict with actual primary constructors if we add them?union Pet(Cat or Dog or Bird) has a nice initial symmetry with patterns, but is that actually a good thing? If we matched on a pattern with
val is (Dog or Cat) dogOrCat, dogOrCat isn't going to magically become an anonymous union of Dog or Cat, it will still be a Pet.allows means the opposite in C# today; it is "everything normally and also ref structs". In this context, it would mean "only the types listed".case does have some symmetry with the proposed case classes, but those are also currently being used to actually declare a new case type. Here, it would just
be the valid options of Pet, not declaring a new case.is has the same issues as the parenthesized or case above.After discussion here, we have a leaning towards the first option: union Pet(Cat, Dog, Bird). We do want to hear from users on this decision though, so we will make
sure to revisit this with feedback in mind to determine if that's the final form, particularly if we also decide to support primary constructors in this area.
We are moving forward with the union Pet(Cat, Dog, Bird) syntax form.
Champion issue: https://github.com/dotnet/csharplang/issues/9662
Specifications:
For our second topic today, we discussed the evolution of ADT hierarchies in C#. There are two possible approaches on the table at the moment: expanding out the
union feature to allow declaration of cases implicitly, or expanding out the existing enum features to allow declaring non-numeric cases. While these both sound
dissimilar at first description, one thing that we realized after discussion is that these ideas are extremely compatible at the semantic level, with different skins
around the same general concept of having a shorthand for declaring an ADT. This is validating for the kernel of the idea itself, as no one is pushing back that this
is generally a thing that we want C# to have. However, it again puts us in a thunderdome of syntax, of union vs enum. One nicety about enum is that it already
has the implication that the cases are newly-declared constructs; for us to use union as the keyword, we'd have to either have an additional modifier on the
union declaration itself, or we'd have to have case or some similar modifier on the type declarations. The enum approach would also give us precedent to
expand enums in other directions; string enums, for example, are a very highly-requested item that we
have thus far avoided due in part to this issue. There is also an opportunity to use this as the point when we refresh enums, allowing the declaration of other
member types that cannot exist in traditional enums today.
This doesn't come without consequences. Today, enums mean numbers. It's going to be a large adjustment for users if this is no longer true, and is generally a very
large shift in the way we think about these things. But we like the direction, and while we haven't settled on exact syntax (is it just enum? Is it enum union? Is
it enum class? Are the cases declared with case? and other such debates), we feel confident in this direction. We expect this to come after the union feature,
building on that as the way that these enums get translated into actual types, but we do expect it to happen.
We will move forward with the enum-centric approach.