digitalmars.dip.ideas - Final Enumerations
- Quirin Schroll (133/133) Oct 15 ## Spec
- Nick Treleaven (6/14) Oct 16 Why is this needed? Non-final enum has `init` set to the first
- Quirin Schroll (7/21) Oct 16 You’re right. I actually didn’t know (and didn’t check) that an
- Paul Backus (6/12) Oct 16 I would rather see stricter rules enforced for existing `enum`
- Quirin Schroll (4/19) Oct 16 Whether `cast(EnumType)42` makes sense depends entirely on your
An enumeration is not a fixed set of values. It’s a selection of values of another type. Add a kind of enumeration type that is a fixed set of values. In Java, enums are fixed sets. them](https://github.com/dotnet/csharplang/blob/main/proposa s/closed-enums.md). (This link will break if a decision on the proposal is made, [permalink](https://github.com/dotnet/csharplang/blob/fac2b6993a2980132a15eaf5189dec7e109ff2af/proposals/closed-enums.md).) The reason why such enumerations are valuable is because it can be ensured at compile-time that a `switch` handles all values. Since D has `final` as a keyword, and it mirrors `final switch`, why not add `final enum` as a kind of enumeration such that the compiler enures values of that type are not outside the range of values defined. Values of final enum type implicitly convert to a type if their base type does. That handles the conversion to the base type itself. That means a `final enum Option : bool { no, yes }` would not suffer from the implicit conversion of `0` or `1` to it, but `Option.no` and `Option.yes` would convert to `0` and `1` implicitly. No grammar change is needed. `final enum` is already valid, however, `final` currently does not change the semantics of enumerations. Technically, this is a breaking change. Either breakage (which is likely rare in practice) is acceptable here or adding this in an edition solves the breakage problem. The usual way to create values of a final enum would be to refer to the enumeration directly. Using a base-type value for the enum without an explicit cast is an error. The explicit cast is ` safe` only if VRP can prove that the base-type value will, in fact, be one of the enum values. Otherwise, it is ` system`; the compiler may add an assertion that the value is in range. (If the range of an enum is a contiguous section of a built-in integral type, that check is cheap, on the order of checking if indices are in range.) Final enum types are types like `bool` and pointers for which it is undefined behavior if they end up having a value not listed in the enumeration. Producing such a value falls into the same category as a `bool` with a bit pattern other than `0x0` or `0x1`, or a non-null pointer that does not point to an object of the appropriate type. This affects `union` types when they’re members. In D, enums can have a wide range of built-in types, including integers, floats, slice types (includes strings), pointers (including function pointers), class types, and associative array types. Checking that a run-time value is equal to one of these can be expensive. If that is deemed inappropriate for a core-language feature, it can be an error for any types for which it is more expensive than simple value comparisons. A final enum always has a value equivalent to its `init` with the default value of its base type. Such a value must exist and it must be named (it need not be unique). This value must be handled by `final switch`, either by referring to `init` or an enumeration constant equal to it (often named `none` or `unknown`). (This restriction could be lifted, and if a final enum lacks a value equal to `init`, it has a hidden enumeration constant named `init` with the default value.) If a non-final `switch` statement handles all cases of a value of final enum type, having a `default:` case is an error (unreachable code), except if the default case is, effectively, just an `assert(0)`. Such `switch` statement must be a `final switch` or the default marked as unreachable. The only pain point about the notion and keyword: A final enum is not final in the sense of inheritance. ```d final enum E0 { x, y, z } final enum E1 : E0 { a, b } // good: `E1.a = E0.x` and `E1.b = E0.y` final enum E2 : E0 { a, b, c, d } // Error: implicit initializer to `d` is out of range of `E0` final enum E3 : E0 { p = E0.a, q = E0.z } // good, need not be contiguous E1 e1 = E1.a; E0 e0 = e1; // good, E1 is a sub type of E0 e1 = cast(E1)e0; // run-time check will succeed, since e0 is in fact E1.a e0 = E0.z; e1 = cast(E1)e0; // run-time check will fail, since e0 > E1.max int x = 12; e0 = x & 0x3; // good: VRP can prove E0.min ≤ `x & 0x3` ≤ E0.max and E0 is contiguous // If VRP isn’t good enough to prove it’s valid, error in safe code: { E3 e3 = x ? E0.x : E0.z; } // If VRP isn’t good enough to prove it’s valid, run-time check succeeds … { E3 e3 = cast(E3)(x ? E0.x : E0.z); } // … but it has to check values individually, since E3 isn’t contiguous. ``` ```d // E0.init is cast(E0)int.init which succeeds and is E0.x // E1.init is E1.a for the same reason // E2 is invalid // E3.init is E3.p for the same reason // restriction intact: final enum E4 : int { y = 1 } // Error, no option for int.init // restriction lifted: final enum E4 : int { y = 1 } // Okay, equivalent to { init = int.init, y = 1 } final enum E5 { init, y } // Good final enum E6 { x, y, init } // Okay final enum E7 { y = 1, init } // Okay final enum E8 { y = 1, init = 0xFF } // Okay // restriction intact: final enum E9 : char { x } // Error, no option for char.init, which is 0xFF // restriction lifted: final enum E9 : char { x } // Okay, equivalent to { x = 0, init = char.init } because char.init is 0xFF ``` ```d E1 e1; final switch (e1) { case E1.a, E1.b: } // Okay switch (e1) { case E.a: default: } // Okay, default handles E.b switch (e1) { case E1.a, E1.b: default: } // Error, all cases handled, default unreachable switch (e1) { case E1.a, E1.b: default: assert(0); } // Okay, default is unreachable, but marked as such E4 e4; // Assume restriction lifted final switch (e4) { case E4.y: } // Error: init not handled final switch (e4) { case E4.y, E4.init: } // Okay switch (e4) { case E4.y: default: } // Okay, default handles init ```
Oct 15
On Wednesday, 15 October 2025 at 11:34:57 UTC, Quirin Schroll wrote:A final enum always has a value equivalent to its `init` with the default value of its base type. Such a value must exist and it must be named (it need not be unique). This value must be handled by `final switch`, either by referring to `init` or an enumeration constant equal to it (often named `none` or `unknown`). (This restriction could be lifted, and if a final enum lacks a value equal to `init`, it has a hidden enumeration constant named `init` with the default value.)Why is this needed? Non-final enum has `init` set to the first member value. I understand maybe wanting to support one of the other members being the `init` value, but why would it have to be the same value as the base type's `init`?
Oct 16
On Thursday, 16 October 2025 at 16:13:48 UTC, Nick Treleaven wrote:On Wednesday, 15 October 2025 at 11:34:57 UTC, Quirin Schroll wrote:You’re right. I actually didn’t know (and didn’t check) that an enum’s `init` is its first value; I thought it would be copying the underlying type’s `init`. Since that’s the case, all the `init` stuff isn’t needed. Good, because I didn’t like it. Making an enum final shouldn’t change what its `init` is.A final enum always has a value equivalent to its `init` with the default value of its base type. Such a value must exist and it must be named (it need not be unique). This value must be handled by `final switch`, either by referring to `init` or an enumeration constant equal to it (often named `none` or `unknown`). (This restriction could be lifted, and if a final enum lacks a value equal to `init`, it has a hidden enumeration constant named `init` with the default value.)Why is this needed? Non-final enum has `init` set to the first member value. I understand maybe wanting to support one of the other members being the `init` value, but why would it have to be the same value as the base type's `init`?
Oct 16
On Wednesday, 15 October 2025 at 11:34:57 UTC, Quirin Schroll wrote:An enumeration is not a fixed set of values. It’s a selection of values of another type. Add a kind of enumeration type that is a fixed set of values. [...]I would rather see stricter rules enforced for existing `enum` types (at least in ` safe` code), but I understand there are practical reasons that may not be feasible. If this is the best we can do, then I support it.
Oct 16
On Thursday, 16 October 2025 at 17:01:40 UTC, Paul Backus wrote:On Wednesday, 15 October 2025 at 11:34:57 UTC, Quirin Schroll wrote:Whether `cast(EnumType)42` makes sense depends entirely on your intention. I don’t think is a great idea to take if off people’s plates.An enumeration is not a fixed set of values. It’s a selection of values of another type. Add a kind of enumeration type that is a fixed set of values. [...]I would rather see stricter rules enforced for existing `enum` types (at least in ` safe` code), but I understand there are practical reasons that may not be feasible. If this is the best we can do, then I support it.
Oct 16









Quirin Schroll <qs.il.paperinik gmail.com> 