First Draft: Nominal Sum Types via `enum union` and `switch` Expressions

Meta jared771 at gmail.com
Mon Sep 21 23:05:42 UTC 2026


On Monday, 21 September 2026 at 14:17:51 UTC, Meta wrote:
> On Sunday, 20 September 2026 at 21:20:29 UTC, Dukc wrote:
>> On Sunday, 20 September 2026 at 02:10:15 UTC, Meta wrote:
>>>> I'm a bit worried this would be like `[1, 2, 3]` being typed 
>>>> as `int[3]`, instead of `int[]` it is, for mostly good 
>>>> reasons.
>>>>
>>>> On the other hand, if `PaymentEvent.CardCharge("my token", 
>>>> 1_000_000_000, "CAD", true)` would be `PaymentEvent` by 
>>>> default that would be inconsistent too, since no other 
>>>> constructor call returns a result of another type.
>>>>
>>>> Maybe we can initialise sum types with struct variants like 
>>>> `PaymentEvent(CardCharge = {"my token", 1_000_000_000, 
>>>> "CAD", true})` for the `PaymentEvent` type.
>>>
>>> You can always use tuple variants instead, which are *not* 
>>> their own type.
>>
>> I wasn't precise enough, I suppose. I wasn't worried about 
>> struct variants having their own type per se, I was worried 
>> about the implications of having that type _inferred by 
>> default_. Although I'm not actually proposing any changes here 
>> since I didn't come up with better ideas.
>>
>>>> An idea: Maybe `case bool;` could alternatively be written 
>>>> as `case this(bool);`. Then `this(int){}` would be a regular 
>>>> constructor function you'll define yourself and which will 
>>>> probably call a `case` constructor.
>>>
>>> Enum unions can already have constructors, so you can define 
>>> `this(bool)` and `this(int)` if you want.
>>
>> Yes, but the idea is that since tuple variants are declared 
>> like functions, you could do the same with bare type variants 
>> using the `this` keyword. Stylistic thing only, but would be 
>> consistent IMO.
>>
>>
>>> Yeah I agree. This is a bad example. A better one is
>>> ```d
>>> enum union Pointers
>>> {
>>>     case int*;
>>>     case void*;
>>> }
>>>
>>> Pointers p = null; // Error, null is ambiguous between 
>>> variants 'int*' and 'void*'
>>> ```
>>
>> Sorry, that is still wrong. Spec 20.14.6:
>>> If two or more functions have the same match level, then 
>>> partial ordering is used to disambiguate to find the best 
>>> match. Partial ordering finds the most specialized function. 
>>> If neither function is more specialized than the other, then 
>>> it is an ambiguity error. Partial ordering is determined for 
>>> functions f and g by taking the parameter types of f, 
>>> constructing a list of arguments by taking the default values 
>>> of those types, and attempting to match them against g. If it 
>>> succeeds, then g is at least as specialized as f.
>>
>> This means that `int*` is more specialised than `void*` and 
>> should therefore be picked.
>
> Ya, I meant for that to be long*, not void*.
>
>>> 2. With your suggestion, this code now becomes ambiguous:
>>> ```d
>>> Integer val = ...;
>>> switch (val)
>>> {
>>>     case numeric(x) => ... // What is typeof(x)? int or long?
>>> }
>>> ```
>>
>> For now, you'd have to specify a type for `x`, just as with 
>> bare type variants. If a future DIP will implement wildcard 
>> matching, `x` will be a wildcard that matches both, probably 
>> either like a templated function or a `case` inside `static 
>> if`.
>
> Rikki included it in his DIP, but I don't really see the point 
> to allowing a wildcard case. The whole idea is that you are 
> intentionally handling each case of the enum union. Also what 
> happens if you have a wildcard case *and* a default arm? And 
> multiple unhandled cases? Which arm captures which cases? I 
> don't think the extra complexity is worth it.
>
>>>
>>> This is outside my vision for the feature, and would be a 
>>> completely separate DIP.
>>>
>>
>> Well you are already specifying your sum types are structs in 
>> disguise, which is likewise out of scope for what is minimally 
>> required for sum types.
>
> How else would they be represented? A struct that contains a 
> union has been the canonical way to do this for the past 50 
> years, and it's a very easy, simple lowering.
>
>> If implemented it will dictate the basic idea how sum types 
>> work. This is a high level strategic design decision that 
>> deserves a lot of scrutiny.
>>
>> But obviously, we do want to pick something and you're the DIP 
>> author so of course it should be something you find 
>> convincing. My personal opinion is that if sum types are 
>> another aggregate types under the hood, and their declarations 
>> say `union` on the tin, they should be unions.
>
> They *are* unions. Tagged unions.
>
>> I guess changing the keyword would be an okay alternative for 
>> me.
>
> `enum union` is just a placeholder name, but it also makes the 
> most sense to me. They have aspects of both enums, with the 
> enumerated cases, and unions, with the overlapping storage. 
> Simple.
>
>>> That's how it was initially, but then I realized that if one 
>>> type in the union disables default construction, you can just 
>>> use the next one in the list. So it's only necessary if _all_ 
>>> of them disable it, unless I'm missing something.
>>>
>>
>> It's not necessary for the language to work, but it's 
>> necessary to avoid an inconsistency with rest of the language. 
>> Users are probably going to expect that untagged and tagged 
>> unions behave similarily on matters like this.
>
> That's true, but the reason it works like that for unions is 
> because they're untagged. The rule is unnecessary for tagged 
> unions.


On further thought, there's a case where this causes unintuitive 
behaviour:
```d
enum union TransactionState
{
     case Pending(ApprovalToken),
     case Rejected,
}
```

If ApprovalToken is changed to disable default construction, then 
the default variant will silently become Rejected... Although 
arguably that should be the default anyway for security reasons, 
but there are likely other examples where this behaviour is not 
ideal, so I'll change it:
The first variant in the union is always the default, and if it 
disables default construction, then it's disabled for the entire 
union. All other variants are free to disable default 
construction without affecting the union.


More information about the dip.development mailing list