Second draft: Sum Type by Struct
Meta
jared771 at gmail.com
Sat Sep 5 16:51:20 UTC 2026
On Saturday, 5 September 2026 at 16:02:08 UTC, Nick Treleaven
wrote:
> On Saturday, 5 September 2026 at 14:54:55 UTC, Richard (Rikki)
> Andrew Cattermole wrote:
>> On 06/09/2026 2:19 AM, Nick Treleaven wrote:
>>> I also think an inferred sumtype result is surprising. What
>>> if I want the result type of the match expression to be
>>> inferred from the common type of all the results, how would I
>>> even write that? I think inferred common type should be the
>>> default, which is consistent with the `?` operator.
>>
>> You would add the casts to each arm.
>
> That can be very verbose, and it's not *inferred*. Suppose the
> results are two class instances that inherit from a common base
> class. With your proposal, instead of getting a base class
> result type, I would get a sum type with the specific class
> types? I don't think that is good ergonomics.
>
>>> There should be some way to manually specify the result type
>>> (which could be a sum type or not), the compiler is not
>>> always going to get the desired type whatever the rules. E.g.
>>> `expr.match -> int { ... }`. And it would indeed be nice to
>>> allow that result type to be another sum type.
>> ```d
>> int res = st.match {
>> (T v) => "str",
>> (U v) => 2
>> }; // error
>> ```
>>
>> Sumtypes widen on assignment, so that isn't an issue if you
>> want a sumtype.
>
> What if I don't want a sumtype result, and I want to specify
> the type in one place? E.g. for my base class scenario.
>
>> The match narrows to a single type, just like a sumtype of one
>> variant becomes that variant's type.
>>
>> It doesn't need more syntax, especially one that I haven't
>> spotted in other languages.
>
> I did some research and you're right about other languages. I
> thought I had seen it somewhere. One reason some languages
> don't need it is (like Rust), they have reverse type inference.
> So they use the expected type:
> ```rs
> let r: i8 = match i { // specify i8 as result type
> n if n <= 1 => 1,
> n => 2,
> };
> ```
>
>>> Also, using a new set of integer promotion rules (however
>>> nice) that aren't compatible with the C rules D inherited
>>> would need much stronger justification, and I doubt its a
>>> good idea. That plus sum type inference makes this DIP more
>>> complicated, and those could be added later in a separate
>>> DIP, if needed.
>>
>> I'm not working from C's promotion rules here, they are
>> primarily for math expressions which this is not.
>>
>> The rules I have worked based upon is the sumtype variants
>> where multiple integer types are errors for unnamed variants.
>> You can't know which variant to construct.
>>
>> So I want a rule that is neither lossy, nor will trigger this
>> error.
>>
>> And no, flow sensitive typing is not something you "add on
>> later". You do it up front or not at all.
>
> That doesn't make sense. As I suggested, a valid design is to
> require there to be a common type for all arms, and if not you
> have to specify the result type manually, once. This design is
> simpler.
>
>>>> TypeScript:
>>>>
>>>> ```typescript
>>>> function evaluateArm(input: "a" | "b"): string | number {
>>>> switch (input) {
>>>> case "a":
>>>> return 42; // This arm evaluates to a number
>>>> (int)
>>>> case "b":
>>>> return "hello"; // This arm evaluates to a string
>>>> }
>>>> }
>>>
>>> I don't know TS, but it looks like the `switch` above does
>>> not have a result type at all. BTW I am concerned that IIUC
>>> the above pattern (which you chose to quote), cannot be
>>> written with your proposal, because it doesn't support
>>> `return` in a match arm (i.e. returning from `evaluateArm`
>>> directly).
>>
>> I wanted statement arm bodies, it was in previous designs.
>>
>> But yeash there is nothing in the expression tree that would
>> allow it (that I could find, or aware of).
>>
>> It would be a massive addition.
>>
>> We could add it later on, since expression just becomes an
>> expression statement.
>
> Not being able to use `return` in a match *expression* is
> understandable. Not having a match *statement* which can use
> `return` seems like a big omission IMO.
You can have arms with return, break, continue, throw, etc. if
you allow their type to resolve to noreturn, and they would refer
to the innermost enclosing control flow construct (or otherwise
it's an error - match/switch expressions themselves cannot break
out early). It's basically saying "execution will not continue
past this point, so this expression does not need to have a
value". This isn't in Rikki's proposal, but it's perfectly sound
from a type theory perspective. I imagine in DMD it'd require the
creating of a "DivergeExpression" AST node or something.
More information about the dip.development
mailing list