Second draft: Sum Type by Struct

Meta jared771 at gmail.com
Fri Sep 4 07:30:45 UTC 2026


On Thursday, 3 September 2026 at 11:45:12 UTC, Richard (Rikki) 
Andrew Cattermole wrote:
> ...

My main critiques:

1. This is a structural approach to discriminated unions that is 
a poor fit for D's (mostly) nominal type system. Specifically, I 
think the implicit widening is a bad idea because it could lead 
to confusion when mixing sumtypes from completely different 
domains which just happen to be implicitly convertible via said 
widening.

2. The justification of being able to chain matches and 
interleave them with range code is not compelling enough to make 
the unfamiliar match syntax worthwhile in my mind (some examples 
of how this will improve existing code or new patterns it would 
enable would help).

3. Also, it makes built in language syntax look like user-written 
delegates, which will be confusing.

4. Match arms being allowed to return separate types and silently 
evaluating to a newly synthesized sumtype is just asking for 
trouble. It's going to be a source of a lot of frustration and 
compiler errors, and potentially even bugs. What other language 
that has sum types does this?

5. "No static foreach and so on support for the match handlers."

For me, this is a hard requirement for any such proposal. Maybe 
others will disagree.

6. "The library form remains fully supported and continues to 
serve code that needs template-computed variant sets or cannot 
migrate. The language form is additive."

I think the minimum baseline for any successful proposal is that 
it can completely replace std.sumtype. Otherwise, what's the 
point? FYI the library solution becomes completely redundant with 
my proposal:

```d
enum union SumType(Variants...)
{
     alias Types = NoDuplicates!Variants;

     static foreach (i, V; Variants)
         static if (isBuiltInType!T)
             case V;
         else
             mixin($"case $(T.stringof) = T;"); // type 
aliasing/punning e.g. case ExternalStruct = ExternalStruct

     // Implement the other member functions from std.sumtype
}
```

And Variant/Algebraic/Nullable for that matter.

7. "Catch-all arm: (x) => 42 — matches any variant, parameter 
type inferred from variant"

How does this work? In std.sumtype it works because the template 
lambda acts as a catch all whose type is inferred, but I don't 
think that can work outside of a template function context (?) Is 
the type of x a synthesized union containing the types that 
weren't matched?

8. "If any unnamed variant has a type whose identifier is None 
(e.g., struct None {}), that variant becomes the default. The tag 
is initialized to that variant's index, and the corresponding 
variant field is initialized to its type's .init."

I don't like this special casing. Why not at least use 
typeof(null) instead, which is already D's built in unit type? 
(And void, but unlike typeof(null), you can't create a value of 
type void). It should be that the .init value of the union is the 
init value of its first syntactically declared member. So you 
just put your None case first, and that becomes the natural 
default.

I think the rest of it looks good. I'm not crazy about the 
sumtype syntax, but it's not a deal breaker (though I think the 
syntax in my proposal is much better 😉).


More information about the dip.development mailing list