On Wednesday, 23 September 2026 at 09:14:43 UTC, Dukc wrote:

>

On Monday, 21 September 2026 at 14:17:51 UTC, Meta wrote:

> >

This means that int* is more specialised than void* and should therefore be picked.

Ya, I meant for that to be long*, not void*.

Now sounds right to me.

> >

Well you are already specifying your sum types are structs in disguise, which is likewise out of scope for what is minimally required for sum types.

How else would they be represented? A struct that contains a union has been the canonical way to do this for the past 50 years, and it's a very easy, simple lowering.

Some inprecision from my part again. I don't actually care much how the compiler will lower the tagged union, that's an implementation detail only unless you spec it. I mean your specification that additional (non-case) non-static fields will behave as in structs (or classes).

It's not unreasonable, but it's not the only choice. The additional fields could alternatively behave as in a regular unions like I proposed, or the compiler might simply reject them. The latter choice would be the minimalist one.

Ah I see. They're useful for having members that are valid for every variant. Initially I had a "common field promotion" rule, where fields that are common to every variant can be accessed directly instead of needing to use a switch expression, but I think allowing the enum union itself to have struct-style fields is a better design.

> > >

I guess changing the keyword would be an okay alternative for me.

enum union is just a placeholder name, but it also makes the most sense to me. They have aspects of both enums, with the enumerated cases, and unions, with the overlapping storage. Simple.

Except the storage stops overlapping if you use non-case fields.

Yes.

> > >

The question is, what are_those invariants in this case? You can specify that a sum type must have a valid tag on destruction or else undefined or unspecified behaviour will follow. But I don't think that's a good idea, since it isn't possible to skip the destructor call of a local variable

Named Return Value Optimization.

If you're returning the sumtyped value, that is. Not always the case.

> >

(except by things like longjmp or foreign language exceptions). Hence my suggestion to specify the destructor will do nothing if the tag is invalid.

You're wildly over-complicating this. If the user wants to mess around and do @system stuff with an enum union, they're on their own.

I'm not sure my suggestion is actually complicating anything. It is literally the simplest behaviour possible to do nothing on invalid tag. My suggestion would basically just ban the compiler from executing a random destructor on an invalid tag or the optimiser assuming the tag will always be valid.

The tag can never be invalid. That's a core assumption of the feature. If the user does some hacky stuff that messes with the tag, they're responsible for making sure it's valid again afterward.

> > >

I basically mean that if you have switch (sum) { case x(y) => smth, ...}, it could either mean that x is a variant and y is a variable declaration for smth, or that x is a function or a constructor called with value y, and sum is matched against the result (as in existing switch statements).

It's invalid to have a function call here, so there's no ambiguity.

Doesn't that feel limiting? Consider matching a primitive type. Do you feel this should not be allowed?

import std.conv;

auto var = case(aString.front)
{
    case '\r' => fun(),
    case '\n' => gun(),
    // Unicode replacement character
    case to!dchar(0xFFFD) => hun(),
};

No, it shouldn't be allowed.

>

Remember, existing switch statements allow this. Why switch expressions wouldn't?

Because switch statements match against values, while switch expressions match against patterns. Patterns only exist at compile time, so a function call cannot be a pattern.

> >

I'm fine with that. If you think it's worthwhile, you will need to show me a very convincing example of why it's useful.

enum union DynamicNumber
{   case long, case float, case double, case error(string);
}

@safe sum(DynamicNumber a, DunamicNumber b) => switch(a)
{   case error(s) => a,
    case numA => switch(b)
    {   case error(s) => b,
        case numB => numA + numB,
    },
};

This is a lot more verbose to do manually with static foreachs, especially if you're going to stick with colon separators for the match cases as opposed to semicolon separators so that you can't place the static foreach inside the switch statement.

The issue is that the only place in the language where this exists is in foreach/static foreach, and in template lambdas. In the foreach/static foreach case, the body is copied N times for N different types. That makes some sense for a loop, but IMO it makes no sense for a switch case. Template lambdas infer the argument type, which can't be done in this case because which variant is active is only known at runtime.

Also, since you're omitting the types here, they would have to be inferred by the compiler. I think it can infer them, and the inferred type would have to be double, but that would be very confusing for the reader. There are a lot of issues with this suggestion.

That being said, you're right that this would be tedious to do with the proposal as it is now.

For this specific DynamicNumber case, this should work (should as in it doesn't right now because static foreach isn't allowed in a switch expression, but should be made to work):

enum union DynamicNumber
{
    case long, float, double, error(string);
}

DynamicNumber sum(DynamicNumber a, DynamicNumber b) @safe
{
    enum errorTag = __traits(variantTag, DynamicNumber, "error");
    if (a.__tag == errorTag)
        return a;

    if (b.__tag == errorTag)
        return b;

    enum isBare(alias V) = __traits(variantKind, V) == "bare";
    alias NumericVariants = Filter!(isBare, __traits(allVariants, DynamicNumber));

    return switch (a)
    {
        // Already handled the error case above, but this is needed to satisfy the exhaustiveness check
        default => assert(0),
        static foreach (V1; NumericVariants)
            static foreach (V2; NumericVariants)
                case V1 v1 => switch (other)
                {
                    case V2 v2 => DynamicNumber(v1 + v2);
                }
    }
}