Second draft: Sum Type by Struct

Richard (Rikki) Andrew Cattermole richard at cattermole.co.nz
Tue Sep 15 15:01:46 UTC 2026


On 05/09/2026 6:19 PM, Meta wrote:
> On Friday, 4 September 2026 at 08:20:42 UTC, Richard (Rikki) Andrew 
> Cattermole wrote:
>> It supports both by-ref and multi-level matching.
>>
>> If we need to expand it we would have the capacity to do so, although 
>> at this stage I don't have a reason to.
> 
> We need full blown pattern matching, or what's the use of introducing a 
> match statement? There's no point to half measures here:

So we can add it later.

The form accepted is very limited so that it can be expanded upon later.

>> 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.

> That being said, the if-let pattern in both Rust and Swift accomplishes 
> this in a much simpler way:
> 
> ```rust
> Option<int> result = Some(99);
> if let Some(n) = result {
>      ...
> } else {
>      ...
> }
> ```

Note that this is a variable declaration, just like D has.

What it doesn't have is the integration of implied variable declaration.

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.

>>> 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.



More information about the dip.development mailing list