First Draft: Nominal Sum Types via `enum union` and `switch` Expressions
Meta
jared771 at gmail.com
Tue Sep 15 22:25:13 UTC 2026
On Tuesday, 15 September 2026 at 17:00:14 UTC, Dennis wrote:
> On Tuesday, 15 September 2026 at 15:30:14 UTC, Meta wrote:
>> Compile times and error messages aren't the main reason I
>> think language-level sum types are useful, (...)
>>
>> [Layout optimization is] not core rationale. I think the DIP
>> stands on its own without that section.
>
> If not for for those 2 items, what is the rationale for adding
> specifically all the described machinery to the language? I
> don't see anything else in the rationale section.
To me, the main things that language-level sum types offer are
stack-based @nogc @safe closed polymorphism with better
ergonomics than a library solution (which includes better error
messages and implicit construction), and *without* having to use
templates.
Also, you can't do implicit construction or branch guards with a
library solution. I've put a lot of thought into how it could be
done, but anything I've come up with is so ugly that it's not
worth bothering.
>> But the error messages are significantly better and more
>> accurate by simple virtue of the fact that the compiler has
>> access to a lot of information that user code doesn't.
>
> Such as?
Well for one thing, std.variant.match and std.sumtype.match take
a list of functions/delegates and template lambdas that it uses
to match again. If you accidentally return the wrong type from
one of the functions:
```d
alias Value = SumType!(int, string);
void main()
{
Value v = "asdf";
v.match!(
(int x) => x * 2,
(string s) => s
);
}
```
```
/dlang/dmd/linux/bin64/../../src/phobos/std/sumtype.d(2020):
Error: expected return type of `int`, not `string`:
return
mixin(handlerName!(matches[caseId]), "(", handlerArgs!caseId,
")");
^
/dlang/dmd/linux/bin64/../../src/phobos/std/sumtype.d(2020):
Return type of `int` inferred here.
/dlang/dmd/linux/bin64/../../src/phobos/std/sumtype.d(1662):
Error: template instance `std.sumtype.matchImpl!(Flag.yes,
function (int x) pure nothrow @nogc @safe => x * 2, function
(string s) pure nothrow @nogc @safe =>
s).matchImpl!(SumType!(int, string))` error instantiating
return matchImpl!(Yes.exhaustive, handlers)(args);
^
onlineapp.d(8): instantiated from here:
`match!(SumType!(int, string))`
v.match!(
^
```
Whereas with a language solution:
```d
enum union Value
{
case int, string
}
Value v = "asdf";
auto result = switch (v)
{
case int x => x * 2,
case string s => s,
};
```
```
compiler/test/runnable/testenumunion.d(88): Error: switch
expression arms must have the same type, not `int` and `string`
case string s => s,
^
```
This isn't a knock against std.sumtype; it's an incredibly
well-designed implementation. But no matter how good the
implementation is, it'll never be able to look inside a delegate
or template lambda passed to `match`, to show the user where
exactly it's failing; and it'll never be able to offer error
messages as good as those a language-level solution can. Not
without AST macros, at least.
But I don't want to frame this as library solution vs. language
solution. I think that'd end in an unproductive debate between
two camps that hold differing views on what belongs in the
language and what belongs in a library. I think the correct
framing is "does this make code written in D safer, more robust,
more elegant, more expressive, and does it give programmers more
power to write their software than they have without it". For me,
the answer is unquestionably yes.
>> Also doing pretty much anything with a library-based sumtype
>> involves a *ton* of template instantiations. You don't need
>> hard numbers to conclude that a language-level implementation
>> will significantly improve on compile times, memory usage,
>> symbol bloat, the optimization the compiler can perform, etc.
>
> I understand the real world correlation between template usage
> and slow compile times is a thing, but I find "library
> therefore templates, templates therefore slow" a bit weak for
> such a big DIP. The DIP doesn't dictate anything about the
> language implementation either. What if it gets implemented by
> lowering to templates in druntime (like Associative Arrays are
> now)?
>
>> It's technically true that a library sumtype could do this
>> type of optimization (although there are still some things the
>> compiler can do that are impossible for a library type)
>
> Such as?
The compiler can lower a switch expression into a jump table,
instead of the nested lambda calls of the current library
solutions. Even if they're aggressively inlined, there's very
little chance they'll get optimized as well as a language
construct can be.
On the memory layout side, imagine a `struct Data { ulong a; uint
b; }`. A library sum type can't reach inside Data to put its
1-byte discriminant into those 4 unused padding bytes.
>> but it's difficult, tedious, and to my knowledge no library
>> solution in D has actually done it.
>
> Why would it be easier in the compiler?
Because the compiler has complete knowledge and control of the
enum union, its contained types, and their respective layouts.
> Btw I'm not speaking as a stickler for std.sumtype who doesn't
> see the appeal in a language solution. However, I do find the
> DIP in its current state very lopsided, focused on the "what"
> and not "why"
Ya, to me the "what" is more important in this case, because
Walter has already expressed a desire to have sum types in the
language in some from. Hell, he wrote a DIP for it himself. But
these are good questions you bring up that I will keep in mind
for future revisions of the DIP.
More information about the dip.development
mailing list