Second draft: Sum Type by Struct
Richard (Rikki) Andrew Cattermole
richard at cattermole.co.nz
Tue Sep 15 16:06:22 UTC 2026
On 16/09/2026 4:00 AM, Meta wrote:
> On Tuesday, 15 September 2026 at 15:01:46 UTC, Richard (Rikki) Andrew
> Cattermole wrote:
>>>> Ocaml
>>>>
>>>> ```ocaml
>>>> type input_type = A | B
>>>>
>>>> let evaluate_arm (input : input_type) =
>>>> match input with
>>>> | A -> `Int 42 (* This arm evaluates to a polymorphic
>>>> variant containing an int *)
>>>> | B -> `String "hello" (* This arm evaluates to a polymorphic
>>>> variant containing a string *)
>>>>
>>>> let () =
>>>> let res = evaluate_arm A in
>>>> match res with
>>>> | `Int n -> Printf.printf "Int: %d\n" n
>>>> | `String s -> Printf.printf "String: %s\n" s
>>>> ```
>>>>
>>>>> 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.
>>>>
>>>> Previous design supported it.
>>>>
>>>> It would be a huge amount of work, for something that could be
>>>> solved with:
>>>>
>>>> ```d
>>>> (v) => () {
>>>> ...
>>>> }()
>>>> ```
>>>
>>> Ah, I thought you meant that it's not possible to generate match arms
>>> with static if.
>>>
>>>> If it is a problem, then maybe a follow up DIP can do it, but I
>>>> don't think the ROI is present for me to do it atm.
>>>>
>>>>> 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:
>>>>
>>>> The point of that statement is that it won't go round breaking code.
>>>>
>>>> This DIP exceeds what std.sumtype can do.
>>>>
>>>>> ```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.
>>>>
>>>> Unfortunately it doesn't solve for result types in general, it can't.
>>>>
>>>> Result types require two language integrations, opCast, and some way
>>>> to check then unwrap like opUnwrapIfTrue.
>>>
>>> 1. D already has opCast
>>> 2. That's an arbitrary requirement you made up that has nothing to do
>>> with Result types.
>>
>> Right, the person who intends to use this as a key design point of
>> PhobosV3, naturally has opinions.
>
> So what happens if your DIP doesn't get accepted? Then you're SOL for
> PhobosV3 and have to rethink your design. Result types *themselves*
> don't need opCast or opUnwrapIfTrue. I think whatever design of PhobosV3
> you have in mind should be kept separate from the design of the rest of
> the language. IMO Language design should drive library design, not vice-
> versa.
Then it becomes a problem for Adam to solve.
Either I get the problems solved that I can foresee, or its not worth my
limmited energy being put towards a library that I think isn't the right
way to go for D's standard library.
>> I'm not convinced that this is the right way to solve it, specifically
>> because result types can have a much larger API and internal logic
>> than just a sumtype.
>
> Like what? I can't picture what you're thinking of.
Future/Promise.
These are result types.
>>>>> 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 would prefer to use identifier types instead, they don't have a
>>>> declaration associated with it. However I don't see a way to get
>>>> them approved right now. Previous designs used them.
>>>>
>>>> It was part of my member of operator work, but due to their implicit
>>>> conversions it was just going to be an uphill battle with Walter
>>>> either not understanding it or not liking it.
>>>
>>> Why do you need that? Just tell the programmer to make the first type
>>> of the union its None value, and adopt the rule that the union's init
>>> value is that first type's init value. It's such a simple solution
>>> rather than having the compiler recognize a special, arbitrary symbol.
>> To try and make it consistent with other types default initialization.
>
> I'm still not following. Having the .init value of the union being the
> tag = 0 (so the first variant is active) and the value stored being
> the .init value of the first type in the union, seems very logical and
> straightforward to me, in addition to being right in line with default
> initialization in the rest of the language, rather than having to
> complicate things with a special rule (which I see as NOT consistent
> with the rest of the language).
Default initialization goes a step further, it specifies a lack of a
value. NaN, null these are key examples of it.
None is the sumtype equivalent.
More information about the dip.development
mailing list