An enum of slice of class can be mutated and it can be mutated even in a `pure` function. Example: class C { string s; this(string a) pure @safe nothrow { s = a; } } enum C[] cs = [ new C("a"), new C("b") ]; void f() pure @safe @nogc nothrow { cs[0].s = "c"; }
I thought it would allocate a new array literal in f(), but it really modifies global state: ``` class C { string s; this(string a) pure @safe nothrow { s = a; } override string toString() {return s;} } enum C[] cs = [ new C("a"), new C("b") ]; void f() pure @safe @nogc nothrow { cs[0].s = "c"; } import std.writeln; void main() { writeln(cs); // [a, b] f(); writeln(cs); // [c, b] } ``` I think the could should be accepted without @nogc, and it would modify a copy of the array.
As enum was intended to be a replacement of C’s `#define` constants, the compiler should thoroughly treat `enum` as a named literal; it can support “mutable” indirections, but a lot more has to be taken. By “mutable” I mean typed non-const, not that actual mutation is valid. An easy way would be to specify that enums are deep-copied at usage site. This might be possible because circular definitions are rejected: class C { C bestie; this() { } this(C friend) { bestie = friend; } } enum C me = new C(you); enum C you = new C(me); // error enum C[] us = [ me, you ]; But you can cheese it: enum C[] us = [ new C, new C ]; shared static this() { us[0].bestie = us[1]; us[1].bestie = us[0]; } void main() { assert(us[0].bestie is us[1]); assert(us[1].bestie is us[0]); assert(us[0].bestie.bestie is us[0]); assert(us[1].bestie.bestie is us[1]); }