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