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