First Draft: Nominal Sum Types via `enum union` and `switch` Expressions

Meta jared771 at gmail.com
Mon Sep 14 06:35:13 UTC 2026


I was inspired by Rikki's DIP 
(https://forum.dlang.org/post/nhbiwarfrlqqffegkhsf@forum.dlang.org) to write my own that introduces sum types from a different direction that I feel is more "D-like". It borrows heavily from Rust and Swift's version of the feature.

Most of the stuff that I talk about in this DIP has already been 
implemented in a POC:
https://github.com/dlang/dmd/pull/23744

The DIP can be found here:
https://github.com/MetaLang/DIPs/blob/enum-unions/DIPs/1NNN-JH.md

--------------------------------------------------------------------

# Nominal Sum Types via `enum union` and `switch` Expressions

| Field           | Value                                         
                   |
|-----------------|-----------------------------------------------------------------|
| DIP:            |                                               
                   |
| Review Count:   |                                               
                   |
| Authors:        | Jared Hanson                                  
                   |
| Implementation: | https://github.com/dlang/dmd/pull/23744       
                   |
| Status:         |                                               
                   |

## Abstract

Add nominal `enum union` declarations and `switch` expressions to 
the D programming language, providing algebraic data types 
(discriminated unions) with unboxed layout, bounded polymorphism, 
and static exhaustiveness checking. An `enum union` lowers to an 
aggregate struct containing an anonymous union payload and a 
discriminant tag. Switch expressions lower to ternary expressions 
and are used to pattern match against these unions at run time to 
enable powerful programming patterns.

## Contents

* [Rationale](https://www.google.com/search?q=%23rationale)
* [Prior Work](https://www.google.com/search?q=%23prior-work)
* [Description](https://www.google.com/search?q=%23description)
* [Breaking Changes and 
Deprecations](https://www.google.com/search?q=%23breaking-changes-and-deprecations)
* [Reference](https://www.google.com/search?q=%23reference)
* [Copyright & 
License](https://www.google.com/search?q=%23copyright--license)
* [History](https://www.google.com/search?q=%23history)

## Rationale

Sum types (also known as tagged unions, discriminated unions, or 
algebraic data types) are a foundational construct in type-safe 
programming. They allow expressing that a value is one of several 
distinct variants, with static guarantees that all cases are 
handled. This eliminates invalid state representations and 
missing branch errors at compile time.

While D supports raw unions, they are untagged, inherently 
`@system` to access, and lack compiler-managed tag coordination, 
destructor synthesis, and branch exhaustiveness. Library 
solutions like `std.sumtype` implement tagged unions via complex 
template metaprogramming. However, library implementations suffer 
from slow compilation throughput, opaque diagnostic errors, and 
the inability to exploit compiler memory layout optimizations 
such as niche optimization.

Prior Work:

- Rust enums, the main inspiration for this feature.
- Swift enums, also a large source of inspiration, and its switch 
expressions.
- C# unions and switch expressions.
- Java switch expressions.
- Odin unions.
- Zig's union(enum).
- D's `std.sumtype` and `std.variant`.
- [Richard (Rikki) Cattermole's DIP for type unions and match 
expressions](https://forum.dlang.org/post/nhbiwarfrlqqffegkhsf@forum.dlang.org).

## Description

This DIP proposes 2 new constructs for the D language: `enum 
union` as a language-level discriminated union type, and switch 
expressions which are used to inspect these unions at runtime.

An enum union is declared using the `enum union` keyword:
```d
enum union NetworkPacket
{
     case Data(const(ubyte)[]),
     case Ping(ulong),
     case Reset(ushort),
     case Heartbeat(),
     case EndOfStream(),
}
```

Variant declarations in an enum union must start with the keyword 
`case`; they represent one of the possible values that an enum 
union may take on. There are different types of variants that 
serve different functions.

## Variant Kinds

### Tuple Variants
Tuple-like variants (or "tuple variants" for short) consist of a 
name and a list of parameters required to construct that variant. 
Each parameter may be named, but it is not required. It is 
allowed to mix named and unnamed (positional) parameters when 
declaring a tuple variant:
```d
enum union DrawCommand
{
     // Unnamed positional parameters
     case MoveTo(double, double),
     case LineTo(double, double),

     // Named positional parameters
     case Circle(double x, double y, double radius),
     case Text(string content, double x, double y, ubyte fontSize),

     // Mixed named and unnamed positional parameters
     case Arc(double, double, double radius, double sweepAngle),
}
```

If a tuple-like variant has 0 parameters (like `Heartbeat` and 
`EndOfStream` in the previous example above), it is declared with 
an empty argument as shown. These are referred to as unit 
variants, and they are equivalent to other unit types in D like 
`void` and `typeof(null)`.

Tuple-like and unit variants **do not** have their own type. They 
are the same type as the containing enum union.

### Struct Variants
Struct variants are declared as a normal struct declaration:
```d
enum union PaymentEvent
{
     case CardCharge {
         string token;
         ulong amountCents;
         string currency;
         bool require3DSecure;
     },

     case BankTransfer {
         string iban;
         string bic;
         ulong amountCents;
         string reference;
     },

     case RefundIssued {
         ulong originalTxId;
         ulong refundAmountCents;
         string reason;
     },
}
```

Note that struct variants are only allowed to declare fields; not 
methods, constructors, destructors, or any other type of 
declaration.

Unlike tuple variants, struct variants are type declarations. 
They're also subtypes of the enum union:
```d
auto charge = PaymentEvent.CardCharge("my token", 1_000_000_000, 
"CAD", true);
assert(is(typeof(charge): PaymentEvent));
```

### Bare Type Variants
Bare-type variants directly embed an external type as a case in 
the enum union without needing to wrap it in a tuple variant:
```d
enum union ConfigValue
{
     bool,
     long,
     double,
     string,
     string[],
}
```

They are useful for defining a type which may be a value of one 
of several different subtypes. Since they do not have names, bare 
type variants are initialized via direct assignment, similar to 
struct assignment constructor syntax:
```d
ConfigValue c = false; // Union holds a value of type bool
c = ["some", "cool", "strings"]; // Union now holds a value of 
type string[]
c = [1, 2, 3]; // Error, no variant `int[]` in enum union 
`ConfigValue`
```

When it is ambiguous which type would be initialized by this 
assignment, the compiler requires the user to disambiguate:
```d
enum union Nums
{
     case int,
     case long,
}

Nums n = 0; // Error, 0 is ambiguous between variants `int` and 
`long` of enum union `Nums`
Nums n = 0L; // Ok
```

Bare type variants can only be _accessed_ via switch expressions, 
which will be discussed later in this DIP.

### Alias Variants
Alias variants are a shorthand syntax that allows aliasing an 
external type to a different name while also declaring it as a 
variant in the enum union. This is useful for embedding types 
that are in different modules, but have conflicting names, or for 
renaming an embedded external type:

```d
enum union KeyInput
{
     case Windows = sys.platform.win32.events.Win32KeyEvent,
     case Wayland = sys.platform.linux.wayland.WaylandKeyEvent,
     case Darwin  = sys.platform.darwin.cocoa.CocoaKeyEvent,
}

auto input = KeyInput.Windows(32, true);
```

This is semantically equivalent to:
```d
enum union KeyInput
{
     alias Windows = sys.platform.win32.events.Win32KeyEvent;
     case Windows,

     ...etc.
}
```

They're also useful for embedding multiple instantiations of a 
templated type, while giving each instantiation a name:
```d
struct RingBuffer(T)
{
     string channelId;
     T[] items;
}

struct Future(T)
{
     ulong taskId;
     T result;
     bool isReady;
}

enum union WorkerTask
{
     case IngestQueue  = RingBuffer!string,
     case PacketStream = RingBuffer!(ubyte[]),
     case MetricResult = Future!double,
}

auto task = WorkerTask.PacketStream("eth0", [[0xAA, 0xBB], 
[0xCC]]);
```

## Enum Union Members

Enum unions are treated as struct declarations internally, which 
contain a union with the declared variant cases, and a `__tag` 
value to track which variant is currently active.

Like other aggregates in D, enum unions can contain members, 
member functions, constructors, destructors, aliases, etc.
```d
enum union NetworkMessage
{
     case Heartbeat(),
     case Text(string content, string encoding),
     case Binary(ubyte[]),
     case Status(int statusCode, string statusText); // 
Terminating semicolon delimits variants

     ulong timestamp;
     uint sequenceNumber;

     this(string rawInput, uint seq = 0, ulong ts = 0)
     {
         if (rawInput == "ping")
             this = Heartbeat;
         else if (rawInput.length == 0)
             this = Status(400, "Empty Payload");
         else
             this = Text(rawInput);

         this.sequenceNumber = seq;
         this.timestamp = ts;
     }

     size_t byteLength()
     {
         return switch (this)
         {
             case Heartbeat            => 0,
             case Text(content, ...)   => content.length,
             case Binary(bytes)        => bytes.length,
             case Status(status, text) => status.sizeof + 
text.length,
         };
     }

     // Can use shorthand method syntax too
     bool isControlFrame() => switch (this)
     {
         case Heartbeat => true,
         default        => false,
     };
}
```

Inside constructors and member functions, `this` refers to the 
union itself, not the currently active variant. Member fields may 
be accessed with `this.<field>`, but not the fields of individual 
variants.

Inside constructors, the compiler uses definite assignment 
analysis to ensure that the union has been properly initialized 
on all code paths.

## .init and Default Construction

Every enum union provides a `.init` value, which by default is 
the `.init` value of its first declared variant (in syntactic 
order). If that variant has an `@disable`'d init value, then the 
`.init` value of the second variant will be used. If all variants 
disable `.init`, the enum union will also have a disabled `.init`.
```d
enum union Option(T)
{
     case None,
     case Some(T),
}

Option!int opt;
assert(opt.__tag == 0);
assert(opt == Option!int.None);
```

* If all types in the enum union disable default construction 
(`@disable this();`), default construction will be disabled for 
the union as well.

## In-Memory Layout

An enum union's layout is equivalent to the layout of a struct 
defined as follows:
```d
struct EnumUnion
{
     ubyte __tag;

     struct __UnitStruct {}
     struct

     union {
         UnitStruct _0;
     }
}

struct NetworkMessage
{
     ubyte __tag;

     // Created once and shared by every unit variant.
     private struct __UnitPayload {}

     private struct __TextPayload
     {
         string content;
         string encoding;
     }

     private struct __BinaryPayload
     {
         ubyte[] __payload;
     }

     struct Status
     {
         int statusCode;
         string statusText;
     }

     private union
     {
         __UnitPayload   __heartbeat;
         __TextPayload   __text;
         __BinaryPayload __binary;
         Status          __status;
     }
}
```

Any member fields declared come after the union payload.

## Memory Safety

The enum union guarantees memory integrity across variant 
transformations:

* **Value-Copy Pattern Bindings**: Pattern match bindings extract 
payloads by value into the arm's lexical scope. This isolates 
bound variables from the parent aggregate, preventing aliasing 
hazards where an active reference could be corrupted by a 
concurrent re-tagging or reassignment of the parent union during 
arm evaluation. Once D has a sound method of tracking ownership 
and borrowing, like Rikki's DFA analyzer, then binding by ref can 
be allowed.

* **RAII Lifecycle Dispatch**: If any variant contains an 
elaborate destructor (`~this()`), the compiler synthesizes an 
aggregate destructor that inspects the discriminant tag and 
invokes the destructor of the active variant.

* **Deterministic Re-Tagging**: Reassigning a sum type from 
variant `A` to variant `B` executes active destruction of `A`, 
writes the new discriminant tag, and blits payload `B` in an 
exception-safe sequence, eliminating use-after-free, memory 
leaks, and type confusion.

* **Rejection of Non-Copyable Payloads**: Because pattern 
extraction relies on value-copy isolation to remain `@safe` 
without a borrow checker, and because DMD currently lacks 
definite assignment analysis and sub-field dynamic drop flags to 
safely relocate unboxed union members without risking 
double-destruction on scope exit, move-only types are rejected at 
declaration time.


## Implicit Construction

Enum unions are implicitly constructed in the following cases: 
the struct-style construction via assignment discussed 
previously, when a function takes an enum union as an argument, 
and when a function returns an enum union:
```d
enum union Option(T)
{
     case None = typeof(null),
     case Some(T),
}

Option!ConfigValue getConfigVal(string name) {
     string[string] config = readConfig("config.csv");
     if (auto val = name in config) with (typeof(return)) {
         return Some(ConfigValue(*val)); // Implicitly constructs 
an Option!ConfigValue
     }

     return null;
}

void applyConfigVal(Option!ConfigValue c);
applyConfigVal(null); // Implicitly constructs 
Option!ConfigValue.None
```

**NOTE:** Only one level of implicit construction takes place. In 
the example above, the ConfigValue passed to Some is not able to 
be implicitly constructed.

## Niche Optimization (not yet implemented)

When an enum union contains unit variants alongside non-nullable 
references, pointers (`T*`), class references, or bounded scalars 
(such as `bool`), the compiler exploits invalid bit patterns to 
encode the unit state:

* `Option!(int*)`: The null pointer address `0x0` represents 
`None`.
* `sizeof(Option!(int*)) == 8` (on 64-bit platforms), incurring 
zero byte overhead for the tag.

## Switch Expressions

Switch expressions are the main way to interact with enum unions. 
They use pattern matching to match the possible variants:
```d
enum union NetworkPacket
{
     case Data(const(ubyte)[]),
     case Ping(ulong),
     case Reset(ushort),
     case Heartbeat(),
     case EndOfStream(),
}

string describePacket(NetworkPacket pkt)
{
     return switch (pkt)
     {
         // Positional payload extraction binding variables by 
value
         case Data(bytes)     => format("Data payload (%d bytes)", 
bytes.length),
         case Ping(timestamp) => format("Ping probe: 
timestamp=%d", timestamp),
         case Reset(code)     => format("Connection reset with 
code %d", code),

         // Parameterless unit variants match directly by tag name
         case Heartbeat       => "Keep-alive heartbeat received",
         case EndOfStream     => "End of transmission stream",
     };
}
```

There may only be **one** pattern per variant. The following will 
not compile:
```d
switch (pkt)
{
     case Data(bytes) => ...,
     case Data(bytes2) => ..., // Error: redundant match arm. 
Pattern is unreachable
}
```

Every arm must start with the `case` keyword, and every arm is 
required to produce a value. All arms must unify to the same type 
via a LUB (Least Upper Bound) calculation. Arms may not contain 
statements; only a single expression that produces the value for 
that arm. Thus, the following will not compile:
```d
     case Data(bytes) => {
         writeln("Received Data payload");
         ...
         return format(...);
     }
```

However, statement blocks can be emulated using an 
immediately-called delegate literal:
```d
     case Data(bytes) => {
         writeln(...);
         ...etc.
         return format(...);
     }(),
```
**NOTE:** when using switch expressions with an enum union, the 
union's variants are automatically inserted into the switch 
expression's scope for convenient access.

There are multiple forms of patterns for matching against values 
in different ways.

## Switch Expression Patterns

### Destructuring Patterns
As shown above, destructuring patterns destructure the enum 
union's variants. Destructuring patterns can be used for unit, 
tuple, and structure variants.

Destructuring patterns allow fields to be omitted using `...` 
syntax:
```d
enum union U
{
     case Unit(),
     case Struct { int n; double d; string s; }
}

switch (U.Struct(42, 6.9, "asdf"))
{
     case Unit() => 1,
     case Struct(n, ...) => n * 2, // Ignores fields d and s
}
```

The `...` syntax allows ALL fields to be omitted:
```d
     case Unit(...) => 1, // This is valid because ... means "0 or 
more fields"
     case Struct(...) => "No access to Struct's fields here",
```

The `...` syntax can also be prefixed with a variable name:
```d
     // case Unit(rest...) => 1, This is also valid, rest = 
AliasSeq!()
     case Struct(n, rest...) => typeof(rest).stringof, // 
AliasSeq!(double, string)
```

This syntax transforms the remaining fields in the struct into an 
AliasSeq, similar to `T...` template syntax. This syntax is also 
supported for tuple and unit variants.

### Variable Patterns
Variable patterns are of the form `case Type name =>`. Their 
syntax mirrors the declaration of a local variable. These 
patterns can be used for any type of variant:
```d
enum union A
{
     case int,
     case Unit(),
     case Tuple(int n, string),
     case Struct { double d; bool b; },
     case MyStruct = ExternalStruct,
}

switch (A.Tuple(42, "asdf"))
{
     case int n => ...,
     case Unit u => ...,
     case Tuple t => ...,
     case Struct s => ...,
     case MyStruct m => ...,
}
```

In the case of the variable `u` declared for the `Unit` arm, `u` 
is equivalent to a unit struct with no fields or members.

### Type Name Patterns
Type Name patterns are the simplest form of pattern. They are of 
the form `case Type =>`, with no identifier. They are also 
supported for any type of variant:
```d
switch (...)
{
     case int => ...,
     case Unit => ...,
     case Tuple => ...,
     case Struct => ...,
     case MyStruct => ...,
}
```

### Exhaustiveness
Switch expressions are required to be exhaustive over the 
variants in the enum union. The following will fail to compile 
with an error listing the variants not covered:
```d
// Error: switch expression is not exhaustive; missing patterns 
...
switch (...)
{
     case Unit => "unit"
}
```

### Default Arms
Switch expressions may have exactly 1 default arm:
```d
switch (...)
{
     case int => ..., // Only want to explicitly handle the int 
case
     default => ...,  // Cover all other cases
}
```

The default arm represents a catch-all "fallback" for variants 
that do not have a match arm. Thus, a switch expression may omit 
arms for any number of variants as long as it has a default arm.

Note that default arms may not access the active variant. The 
following is invalid:
```d
switch (...)
{
     case int => ...,
     // default val => ..., Error
}
```

### Pattern Guards
Switch arms may have a **Pattern Guard** which is declared with 
the following syntax:
```d
switch (...)
{
     case Data(bytes) if (bytes.length > 10) => ...,
     case Data(bytes) => ...,
     ...etc.
}
```

The expression inside the `if (...)` must evaluate to a bool, and 
the arm will only be taken if it evaluates to true (otherwise, 
it's skipped).

Guarded arms **do not** contribute to exhaustiveness; thus, while 
there **must** be exactly 1 unguarded arm for each variant, and 
there may be any number of guarded arms, the switch expression is 
considered inexhaustive if there are only guarded arms for a 
given variant (unless it has a default arm):
```d
//Error: switch expression is not exhaustive
switch (...)
{
     case Data(bytes) if (bytes.length > 10) => ...,
     case Data(bytes) if (bytes.length == 0) => ...,
     case Data(bytes) if (bytes.length == 42) => ...,
}

// Ok
switch (...)
{
     case Data(bytes) if (bytes.length > 10) => ...,
     case Data(bytes) if (bytes.length == 0) => ...,
     case Data(bytes) if (bytes.length == 42) => ...,
     default => ...,
}
```

Duplicate guarded arms are allowed, but only the first one (in 
syntactic order) will execute; any duplicate guarded arms are 
dead code:
```d
switch (...)
{
     case Data(bytes) if (bytes.length > 10) => "greater than 10",
     case Data(bytes) if (bytes.length > 10) => assert(0), // This 
will never execute
     ...
}
```

## Side-Effects
While switch expressions are expressions, not statements like D's 
regular switch and final switch constructs, they are also allowed 
in statement position:
```d
NetworkPacket pkt = ...;

// Ok
switch (pkt)
{
     case Data(bytes)     => writeln("Got Data"),
     case Ping(timestamp) => writeln("Got Ping"),
     case Reset(code)     => writeln("Got Reset"),
     case Heartbeat       => writeln("Got Heartbeat"),
     case EndOfStream     => writeln("Got EndOfStream"),
}
```

However, the compiler analyzes each arm of the switch expression 
to determine whether they have side-effects or not. If the switch 
expression is in statement position and none of the arms have 
side-effects, the compiler will report an error:
```d
// Error: switch expression has no effect; use `cast(void)` to 
discard its value
switch (pkt)
{
     case Data(bytes)     => format("Data payload (%d bytes)", 
bytes.length),
     case Ping(timestamp) => format("Ping probe: timestamp=%d", 
timestamp),
     case Reset(code)     => format("Connection reset with code 
%d", code),
     case Heartbeat       => "Keep-alive heartbeat received",
     case EndOfStream     => "End of transmission stream",
}

// Ok
cast(void)switch (pkt)
{
     case Data(bytes)     => format("Data payload (%d bytes)", 
bytes.length),
     case Ping(timestamp) => format("Ping probe: timestamp=%d", 
timestamp),
     case Reset(code)     => format("Connection reset with code 
%d", code),
     case Heartbeat       => "Keep-alive heartbeat received",
     case EndOfStream     => "End of transmission stream",
}; // Ending semicolon required
```

## Metaprogramming

### Traits

New traits will be added for working with enum unions and their 
variant cases:
- `__traits(allVariants, E)` takes an enum union E and returns a 
sequence of symbols for each of its variants, in lexical order.
- `__traits(hasVariant, E, Key)` takes an enum union E and a 
string OR a type Key. If the argument is a string, returns true 
if E declares a unit, tuple, record, or aliased variant whose 
identifier equals Key. If it's a type, returns true if E declares 
a bare type whose canonical base type matches Key.
- `__traits(getVariant, E, Key)` similar to `hasVariant`, except 
it directly resolves to the variant symbol or canonical type (for 
bare variants). If Key does not exist in E, it is a compile error.
- `__traits(variantTag, V)` takes a symbol of one of the variants 
of an enum union, and returns a numeric value representing its 
tag.
- `traits(variantParams, V)` takes a symbol of one of the 
variants of an enum union, and returns the parameter tuple for 
its constructor. E.g.:
```d
struct ExternalStruct
{
     int n;
}

enum union Vals
{
     case Unit(),                        // returns AliasSeq!()
     case Tuple(int, string),            // returns AliasSeq!(int, 
string)
     case Struct { bool b; double d; },  // returns 
AliasSeq!(bool, double)
     case int,                           // returns AliasSeq!()
     case MyStruct = ExternalStruct,     // returns AliasSeq!()
}
```
For unit variants, bare type variants, and alias variants, and 
empty list is returned.
- `traits(variantParamNames, V)` like `variantParams`, this trait 
takes a symbol of one of the variants of an enum union, but 
returns the parameter *names* tuple for its constructor, instead 
of the types. Unnamed parameters are represented as an empty 
string.
```d
enum union Vals
{
     case Unit(),                          // returns AliasSeq!()
     case Tuple(int n, string),            // returns 
AliasSeq!("n", "")
     case TupleWithNames(int n, string s), // returns 
AliasSeq!("n", "s")
     case Struct { bool b; double d; },    // returns 
AliasSeq!("b", "d")
     case int,                             // returns AliasSeq!()
     case MyStruct = ExternalStruct,       // returns AliasSeq!()
}
```
- `traits(variantDeclarationOf, V)` this is intended for 
metaprogramming. It takes a variant symbol V, and transforms it 
into a variant declaration as it would appear inside an enum 
union:
```d
enum union A
{
     case int,
     case Unit(),
     case Tuple(int n, string),
     case Struct { double d; bool b; },
     case MyStruct = ExternalStruct,
}

enum union B
{
     case Unit(),

     static foreach (V; __traits(allVariants, A))
         static if (__traits(identifier, V) == "Unit")
             case __traits(variantDeclarationOf, V, "Unit_A");
         else
             case __traits(variantDeclarationOf, V);
}

Now the variants in B are equivalent to if it was declared as:
enum union B
{
     case Unit(),
     case int,
     case Unit_A(),
     case Tuple(int n, string),
     case Struct { double d; bool b; },
     case MyStruct = ExternalStruct,
}
```
Variants with identical signatures are automatically merged by 
`variantDeclarationOf`. Therefore, if the "Unit_A" string 
argument were omitted (or if an empty string was provided) for 
the duplicate `case Unit()` from A, it would not be a compile 
error; the compiler would simply discard one of the duplicates.

This allows powerful metaprogramming patterns like:
```d
/// Tests if two variant declarations match identically across 
all structural dimensions.
template IsExactMatch(alias V1, alias V2)
{
     // 1. Bare types and Alias variants: compare underlying types 
via `is(...)`
     static if ((__traits(variantKind, V1) == "bare" && 
__traits(variantKind, V2) == "bare") ||
                (__traits(variantKind, V1) == "alias" && 
__traits(variantKind, V2) == "alias"))
     {
         static if (__traits(variantKind, V1) == "alias")
             enum bool IsExactMatch = (__traits(identifier, V1) == 
__traits(identifier, V2)) && is(V1 == V2);
         else
             enum bool IsExactMatch = is(V1 == V2); // Bare types 
have no identifier token
     }
     // 2. Tuple, Record, and Unit variants: compare identifier, 
types, and parameter names
     else static if (__traits(variantKind, V1) == 
__traits(variantKind, V2))
         enum bool IsExactMatch =
             (__traits(identifier, V1) == __traits(identifier, 
V2)) &&
             is(__traits(variantParams, V1) == 
__traits(variantParams, V2)) &&
             (__traits(variantParamNames, V1) == 
__traits(variantParamNames, V2));
     else
         enum bool IsExactMatch = false;
}

/// Resolves naming: preserves unique/exact variants via "", or 
namespaces schema conflicts.
template ResolveName(alias V, A, B)
{
     static if (__traits(variantKind, V) == "bare")
         // Bare types never have identifiers or aggregate 
parents; deduplicated by canonical type
         enum string ResolveName = "";
     else
     {
         enum string id = __traits(identifier, V);
         static if (__traits(hasVariant, A, id) && 
__traits(hasVariant, B, id))
         {
             alias VA = __traits(getVariant, A, id);
             alias VB = __traits(getVariant, B, id);
             static if (IsExactMatch!(VA, VB))
                 // Exact declaration match: pass "" so compiler 
collapses them
                 enum string ResolveName = "";
             // Divergent schemas: prefix using provenance
             else static if (__traits(isSame, __traits(parent, V), 
A))
                 enum string ResolveName = A.stringof ~ "_" ~ id;
             else
                 enum string ResolveName = B.stringof ~ "_" ~ id;
         }
         else
             // Unique named variant: retain original name
             enum string ResolveName = "";
     }
}

/// Merges two enum union types: collapses exact matches and 
disambiguates schema conflicts.
enum union Merge(A, B)
if (is(A == enum union) && is(B == enum union))
{
     static foreach (V; AliasSeq!(__traits(allVariants, A), 
__traits(allVariants, B)))
         case __traits(variantDeclarationOf, V, ResolveName!(V, A, 
B));
}

template IsInUnion(alias V, Target)
{
     static if (__traits(variantKind, V) == "bare")
         alias Key = V;
     else
         enum string Key = __traits(identifier, V);

     static if (__traits(hasVariant, Target, Key))
         enum bool IsInUnion = IsExactMatch!(V, 
__traits(getVariant, Target, Key));
     else
         enum bool IsInUnion = false;
}

/// Yields an enum union containing only variants declared 
identically in both A and B.
enum union Intersect(A, B)
if (is(A == enum union) && is(B == enum union))
{
     static foreach (V; __traits(allVariants, A))
     {
         static if (isInUnion!(V, B))
             case __traits(variantDeclarationOf, V);
     }
}

/// Yields an enum union containing all variants of A except 
those shared with B.
enum union Difference(A, B)
if (is(A == enum union) && is(B == enum union))
{
     static foreach (V; __traits(allVariants, A))
     {
         static if (!isInUnion!(V, B))
             case __traits(variantDeclarationOf, V);
     }
}

/// Retains variants unique to either A or B, disambiguating 
colliding schemas via Merge.
alias SymmetricDifference(A, B) = Merge!(Difference!(A, B), 
Difference!(B, A));
```

## Breaking Changes and Deprecations

Because `enum union` reuses existing keywords (`enum`, `union`, 
`case`, `switch`) in a previously illegal syntactic sequence, no 
user code or symbols are broken. Prefix `switch (...) { ... }` in 
expression contexts is distinguished from statement switches by 
the presence of fat-arrow `=>` arms and comma separators.

## Reference

## Copyright & License

Copyright (c) 2026 by the D Language Foundation[cite: 1]

Licensed under [Creative Commons Zero 
1.0](https://www.google.com/search?q=https://creativecommons.org/publicdomain/zero/1.0/legalcode.txt)

## History



More information about the dip.development mailing list