www.digitalmars.com         C & C++   DMDScript  

digitalmars.dip.ideas - Enums with indirections

reply Quirin Schroll <qs.il.paperinik gmail.com> writes:


The general idea behind D’s enums is that they’re “named 
literals,” and a replacement for value-like C macros, i.e. those 
that are used as expressions for objects. At least that’s how I 
model them in my head. You have a literal and you want to have a 
name to refer to it – because it conveys intent, because it’s a 
complicated expression you don’t want to repeat, because it 
references symbols inaccessible/private in your scope, or for 
whatever reason. An enum quite different from an `immutable` 
static variable, which e.g. has an address, is `immutable`, and 
can be initialized at program startup, i.e. at run-time.



Enums with indirections are tricky, but my idea is―I hope―both 
sane and simple:
1. The expression initializing an enum must be admissible to 
initialize a `static immutable` variable at compile-time.
2. When such an enum is used, the initializing expression is 
evaluated at the place of the evaluation of the enum to create 
the value anew.



Condition 1 ensures that they cannot reference mutable global 
state, which they arguable shouldn’t be able to. This does not 
require the enum’s type to actually be immutable, though, and 
thus won’t itself bar later modifications.

Condition 2 ensures that the possibly-mutable parts of the object 
graph are duplicated on use. Only immutable parts can be shared 
between evaluations.

The primary difference between a `pure` factory function with no 
arguments and an `enum` is intent and that `enum`s can be grouped 
and have `switch` support. Technically, using an `enum` is close 
to invoking a `pure` factory function.



Well-known phenomenon that an enum of slice type allocates a 
dynamic array when evaluated.
```d
enum int[] values = [1, 2, 3];

void main()
{
     int[] xs = values, ys = values;
     // as if:
     int[] xs = [1, 2, 3], ys = [1, 2, 3];

     assert(xs !is ys);
}
```

Simple case with two layers of indirections: Everything is newly 
allocated.
```d
enum int[][] values = [[1, 2], [1, 2]];

void main()
{
     auto xs = values, ys = values;

     // As if:
     int[] xs = [[1, 2], [1, 2]], ys = [[1, 2], [1, 2]];

     assert(xs !is ys);
     assert(xs[0] !is ys[0]);
     assert(xs[0] !is xs[1]);
}
```

The above showcase how it’s handled by the language today and 
this is also what I propose. This isn’t true anymore if we modify 
the two layers of indirections a bit:
```d
enum int[][] values = () {
     auto v = [1, 2];
     return [v, v];
}();

void main()
{
     auto xs = values, ys = values;

     // Currently: As if
     int[][] xs = [[1, 2], [1, 2]], ys = [[1, 2], [1, 2]];

     assert(xs !is ys);
     assert(xs[0] !is ys[0]);
     assert(xs[0] !is xs[1]);

     // Proposed: As if
     int[][] xs = () { auto v = [1, 2]; return [v, v]; }();
     int[][] ys = () { auto v = [1, 2]; return [v, v]; }();

     assert(xs !is ys); // same
     assert(xs[0] !is ys[0]); // same
     assert(xs[0]  is xs[1]); // inverted
}
```

With 2 or more levels, a slice can contain two pointers that 
point to the same object. A literal of this object is impossible 
to spell out directly (i.e. without a lambda), but it’s worth 
being able to have it. The factory function for `values` can call 
`dup` on one of the uses of `v` to explicitly decouple the two 
entries, but with the current language semantics, the non-tree 
layout isn’t possible. If we take the idea to heart that an enum 
ought to be like a C macro in that the use of the enum is 
replaced by the definition in a rather mindless way (except that 
while in C, the replacement is syntactic and in D would be 
semantic), it’s seems to me that complex object graphs should be 
admissible and retained as specified in the creation expression. 
It makes the language more consistent, too: Normally you need 
`dup`, but in this niche case, you don’t, leading to programmers 
being surprised.
May 13
parent reply Dukc <ajieskola gmail.com> writes:
On Wednesday, 13 May 2026 at 16:20:43 UTC, Quirin Schroll wrote:
 This isn’t true anymore if we modify the two layers of 
 indirections a bit:
 ```d
 enum int[][] values = () {
     auto v = [1, 2];
     return [v, v];
 }();

 void main()
 {
     auto xs = values, ys = values;

     // Currently: As if
     int[][] xs = [[1, 2], [1, 2]], ys = [[1, 2], [1, 2]];

     assert(xs !is ys);
     assert(xs[0] !is ys[0]);
     assert(xs[0] !is xs[1]);

     // Proposed: As if
     int[][] xs = () { auto v = [1, 2]; return [v, v]; }();
     int[][] ys = () { auto v = [1, 2]; return [v, v]; }();

     assert(xs !is ys); // same
     assert(xs[0] !is ys[0]); // same
     assert(xs[0]  is xs[1]); // inverted
 }
 ```
Yes, I think this should behave as you propose. I'm not sure this warrants a DIP though. I'm inclined to think this is more like a bug.
May 13
parent Quirin Schroll <qs.il.paperinik gmail.com> writes:
On Wednesday, 13 May 2026 at 16:44:43 UTC, Dukc wrote:
 I'm not sure this warrants a DIP though. I'm inclined to think 
 this is more like a bug.
You’re right, this might be what it ought to be already. https://github.com/dlang/dmd/issues/23145
May 18