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

Meta jared771 at gmail.com
Sat Sep 26 00:47:37 UTC 2026


On Friday, 25 September 2026 at 20:44:26 UTC, Dukc wrote:
> On Wednesday, 23 September 2026 at 22:15:09 UTC, Meta wrote:
>> The tag can never be invalid. That's a core assumption of the 
>> feature. If the user does some hacky stuff that messes with 
>> the tag, they're responsible for making sure it's valid again 
>> afterward.
>
> It's certainly possible to specify the destructor that way. 
> I've already written my main counterarguments to it, so I guess 
> it's time we agree to disagree on this point.

Ya, I feel strongly that it's a bad idea to silently skip the 
destructor if __tag is invalid. I think that'd be a C++-level 
footgun.

>>> Remember, existing `switch` statements allow this. Why 
>>> `switch` expressions wouldn't?
>>
>> Because switch statements match against values, while switch 
>> expressions match against patterns. Patterns only exist at 
>> compile time, so a function call cannot be a pattern.
>
> On second thought, maybe I agree with your assessment on 
> function calls after all. Even if function calls would be 
> allowed, only CTFE calls would work and the results of those 
> can be declared beforehand with `enum`.
>
> Template instancing should still be allowed inside the body 
> though, that template instance might be an alias referring to a 
> `case` field.

I don't know what you mean by template instances in this case. 
Are you talking about this?
```d
struct S(T);
enum union E(T) { case S!T; }

switch(...)
{
     case S!T, T => writeln(T.stringof)
}
```

I wanted to do something like this, but when I looked into it, it 
wasn't possible for some reason that I am forgetting right now. 
It would be nice, though.

>>
>> Also, since you're omitting the types here, they would have to 
>> be inferred by the compiler. I *think* it can infer them, and 
>> the inferred type would have to be double,
>
> Well, `long` in case of both `a` and `b` being longs. I admit I 
> didn't get the result typing as I wished in this example.
>
> I managed even worse in my "better" example in the next post 
> since the `case (float aNum, float bNum)` will match all 
> non-error cases, which means nothing would be left to match for 
> the wildcard `case (aNum, bNum)`!
>
>> but that would be very confusing for the reader. There are a 
>> lot of issues with this suggestion.
>
> Had another look how OCaml does it. It does support wildcards, 
> but they do not work as I proposed. Instead, they are 
> essentially `default` cases with a variable name, the variable 
> binding to result of the matched expression. I think that's a 
> good alternative.

The problem with that is:
```d
enum union U { case int, string, bool, Unit() }

switch(U.init)
{
     default v => ...
}
```

A default arm allows you to omit any number of other cases. The 
problem is, how do you unify the types of the omitted cases when 
they can easily all be invariant with each-other? What is the 
type of `v` within the type system?

Rikki did include this in his DIP, and his solution was that it 
acts like a template lambda or a static foreach loop. The arm is 
duplicated automatically by the compiler for each type. I don't 
like this, because it's not at all clear for the user that this 
is happening. You can't even access `u`'s members, because there 
is no way for the compiler to compute a type for it in the 
general case (because D doesn't have a language-level Any type). 
And if you need to access the value in the default arm, there are 
two options:
```d
U u;
switch (u)
{
     default => writeln("u = ", u),
}

//OR
switch (auto u = U.init)
{
     ...
}
```

> Why would we want it if we already have the `default` case? 
> Logical consistency. In the future, we can probably do
> ```d
> switch(someOptionalInt)
> {   case just 0 () => "zero",
>     case just(x) => "value",
>     case nothing() => "no value"
> }
> ```
> . Note that the syntax can't be `case just(0)`, because then we 
> couldn't know whether `x` in `just(x)` is a value to match (an 
> `enum` identifier) or a variable declaration.

Right, and this is why I don't want to allow expression 
evaluation inside a pattern. *Maybe* it could be allowed with 
some special syntax to distinguish... I'll think more about it.

> It'd make sense that the syntax for the top level case would 
> work similarily, which leads to:
>
> ```d
> switch(someNormalInt)
> {   case 0 () => "zero",
>     case x => "value"
> }
> ```




More information about the dip.development mailing list