www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Stop using the enum as a manifest constant.

reply Jack Applegame <japplegame gmail.com> writes:
Just don't do it.
Use `const` or `immutable`.
If you want to force CTFE, then use `static const` or `static 
immutable`.

First, using an enumerable as a constant is quite strange in 
itself.

Second, look at this:

```d
pragma(inline, false)
void sideEffect(const int[] arr)  nogc {
     import core.volatile;
     volatileLoad(cast(uint*) arr.ptr);
}

int[] genArray(size_t length) {
     auto result = new int[length];
     foreach(i, ref e; result) e = cast(int) i;
     return result;
}

void useEnum() {
     enum f1 = genArray(3);
     sideEffect(f1);
     sideEffect(f1);
}

void useConst() {
     static const f1 = genArray(3);
     sideEffect(f1);
     sideEffect(f1);
}
```
Assembler output (ldc2 -O3):
```asm
void example.useEnum():
         push    r14
         push    rbx
         push    rax
         mov     r14, qword ptr [rip + 
TypeInfo_xAi.__init GOTPCREL]
         mov     esi, 3
         mov     rdi, r14
         call    _d_newarrayU PLT
         movabs  rbx, 4294967296
         mov     qword ptr [rdx], rbx
         mov     dword ptr [rdx + 8], 2
         mov     edi, 3
         mov     rsi, rdx
         call     nogc void example.sideEffect(const(int[])) PLT
         mov     esi, 3
         mov     rdi, r14
         call    _d_newarrayU PLT
         mov     qword ptr [rdx], rbx
         mov     dword ptr [rdx + 8], 2
         mov     edi, 3
         mov     rsi, rdx
         add     rsp, 8
         pop     rbx
         pop     r14
         jmp      nogc void example.sideEffect(const(int[])) PLT

void example.useConst():
         push    rbx
         lea     rbx, [rip + .dynarrayStorage]
         mov     edi, 3
         mov     rsi, rbx
         call     nogc void example.sideEffect(const(int[])) PLT
         mov     edi, 3
         mov     rsi, rbx
         pop     rbx
         jmp      nogc void example.sideEffect(const(int[])) PLT

.dynarrayStorage:
         .long   0
         .long   1
         .long   2
```

We should ban it forever.
~~`enum a = 10;`~~
`const a = 10;`
Jun 06
parent reply evilrat <evilrat666 gmail.com> writes:
On Sunday, 6 June 2021 at 09:49:11 UTC, Jack Applegame wrote:
 If you want to force CTFE, then use `static const` or `static 
 immutable`.
What did you expected from an enum array? It is known to allocate every time you reference it (except maybe for strings), have you just noticed this?
Jun 06
parent reply Jack Applegame <japplegame gmail.com> writes:
On Sunday, 6 June 2021 at 09:53:09 UTC, evilrat wrote:
 On Sunday, 6 June 2021 at 09:49:11 UTC, Jack Applegame wrote:

 What did you expected from an enum array?
The same output as when using a `static const` array.
 It is known to allocate every time you reference it (except 
 maybe for strings), have you just noticed this?
It's a well-known "feature" of enum. And it's one of the reasons why we should ban such use of it.
Jun 06
parent reply evilrat <evilrat666 gmail.com> writes:
On Sunday, 6 June 2021 at 10:09:03 UTC, Jack Applegame wrote:
 On Sunday, 6 June 2021 at 09:53:09 UTC, evilrat wrote:
 On Sunday, 6 June 2021 at 09:49:11 UTC, Jack Applegame wrote:

 What did you expected from an enum array?
The same output as when using a `static const` array.
 It is known to allocate every time you reference it (except 
 maybe for strings), have you just noticed this?
It's a well-known "feature" of enum. And it's one of the reasons why we should ban such use of it.
Nah, that enum arrays specifically, deprecating this specific case is probably ok though.
Jun 06
parent reply Jack Applegame <japplegame gmail.com> writes:
On Sunday, 6 June 2021 at 10:24:54 UTC, evilrat wrote:
 Nah, that enum arrays specifically,
Except for character arrays. Just change `int[]` to `char[]`/`wchar[]`/`dchar[]`. ```asm void example.useEnum(): push rbx lea rbx, [rip + .L.str] mov edi, 3 mov rsi, rbx call nogc void example.sideEffect(const(char[])) PLT mov edi, 3 mov rsi, rbx pop rbx jmp nogc void example.sideEffect(const(char[])) PLT void example.useConst(): push rbx lea rbx, [rip + .L.str] mov edi, 3 mov rsi, rbx call nogc void example.sideEffect(const(char[])) PLT mov edi, 3 mov rsi, rbx pop rbx jmp nogc void example.sideEffect(const(char[])) PLT ```
 deprecating this specific case is probably ok though.
That's not enough. Let's deprecate any use of enum as a manifest constant.
Jun 06
parent reply Mathias LANG <geod24 gmail.com> writes:
On Sunday, 6 June 2021 at 10:40:13 UTC, Jack Applegame wrote:
 That's not enough. Let's deprecate any use of enum as a 
 manifest constant.
I wouldn't go that far. `enum` are like `#define`, and there *are* cases where you want them. E.g. when using them as define arguments: ```D void foo (char[] fmt = DEFAULT_VALUE) { /* ... */ } ``` If `DEFAULT_VALUE` is `immutable`, this will never compile, but with `enum`, it might. You'd think that's an odd use case, but we actually had a few instances at Sociomantic which triggered when we tried to change some internal tools to use `static immutable` instead of `enum`. I agree with the wider point though. People expect manifest constant to have an address and to be eagerly evaluated. That's a [`static`] `immutable`. `enum` is just a copy-pasted literal.
Jun 06
next sibling parent reply Jack Applegame <japplegame gmail.com> writes:
On Sunday, 6 June 2021 at 10:47:54 UTC, Mathias LANG wrote:
 I wouldn't go that far. `enum` are like `#define`, and there 
 *are* cases where you want them. E.g. when using them as define 
 arguments:
 ```D
 void foo (char[] fmt = DEFAULT_VALUE) { /* ... */ }
 ```

 If `DEFAULT_VALUE` is `immutable`, this will never compile, but 
 with `enum`, it might.
Really? ```d enum DEFAULT_VALUE = "aaaa"; void foo (char[] fmt = DEFAULT_VALUE) {} // Error: cannot implicitly convert expression `"aaaa"` of type `string` to `char[]` ```
Jun 06
parent reply Mathias LANG <geod24 gmail.com> writes:
On Sunday, 6 June 2021 at 11:16:29 UTC, Jack Applegame wrote:
 On Sunday, 6 June 2021 at 10:47:54 UTC, Mathias LANG wrote:
 I wouldn't go that far. `enum` are like `#define`, and there 
 *are* cases where you want them. E.g. when using them as 
 define arguments:
 ```D
 void foo (char[] fmt = DEFAULT_VALUE) { /* ... */ }
 ```

 If `DEFAULT_VALUE` is `immutable`, this will never compile, 
 but with `enum`, it might.
Really? ```d enum DEFAULT_VALUE = "aaaa"; void foo (char[] fmt = DEFAULT_VALUE) {} // Error: cannot implicitly convert expression `"aaaa"` of type `string` to `char[]` ```
It *might*, e.g. the following: ```D enum DEFAULT = foo(); char[] foo () { return null; } void bar (char[] arg = DEFAULT); ```
Jun 06
parent reply Jack Applegame <japplegame gmail.com> writes:
On Sunday, 6 June 2021 at 13:21:35 UTC, Mathias LANG wrote:
 It *might*, e.g. the following:
 ```D
 enum DEFAULT = foo();
 char[] foo () { return null; }
 void bar (char[] arg = DEFAULT);
 ```
Mutable enum??? WAT? It's absolutely unacceptable. ```d char[] foo() safe { return "hello".dup; } enum WAT = foo(); void main() safe { WAT[0] = 'w'; writeln(WAT); } ``` ```shell Error: program killed by signal 11 ```
Jun 06
parent reply Mathias LANG <geod24 gmail.com> writes:
On Sunday, 6 June 2021 at 14:05:54 UTC, Jack Applegame wrote:
 On Sunday, 6 June 2021 at 13:21:35 UTC, Mathias LANG wrote:
 It *might*, e.g. the following:
 ```D
 enum DEFAULT = foo();
 char[] foo () { return null; }
 void bar (char[] arg = DEFAULT);
 ```
Mutable enum??? WAT? It's absolutely unacceptable. ```d char[] foo() safe { return "hello".dup; } enum WAT = foo(); void main() safe { WAT[0] = 'w'; writeln(WAT); } ``` ```shell Error: program killed by signal 11 ```
This is... interesting. I did a bit of digging in memory and the code was actually using `int[]` (or at least, not `char[]`), so it was something like this: ```D import std.stdio; int[] foo() safe { return [42, 42, 42]; } enum WAT = foo(); void main() safe { bar(); } void bar(int[] data = WAT) safe { data[0] = 84; writeln(data); writeln(WAT); } ``` The above works as expected (at least, as *I* would expect). However, change the type to `char[]` and it SEGV. That's probably because string literals are special and the compiler makes some (bad) assumptions. However, the compiler shouldn't allow you to use an enum as an lvalue. It used to be able to recognize this, but it broke at some point after v2.080.0.
Jun 06
next sibling parent Jack Applegame <japplegame gmail.com> writes:
On Sunday, 6 June 2021 at 15:05:47 UTC, Mathias LANG wrote:
 ```D
 import std.stdio;
 int[] foo()  safe { return [42, 42, 42]; }
 enum WAT = foo();
 void main()  safe {
     bar();
 }
 void bar(int[] data = WAT)  safe
 {
     data[0] = 84;
     writeln(data);
     writeln(WAT);
 }
 ```

 The above works as expected (at least, as *I* would expect). 
 However, change the type to `char[]` and it SEGV. That's 
 probably because string literals are special and the compiler 
 makes some (bad) assumptions.
It's definetly a code smell. Here are much better solutions: ```d char[] foo() pure { return "hello".dup; } immutable DEFAULT = foo(); void bar(char[] data = DEFAULT.dup) { data[0] = '1'; // DEFAULT[0] = '2'; Error: cannot modify `immutable` expression `DEFAULT[0]` writeln(data); writeln(DEFAULT); } ``` It uses explicit copying instead of the undocumented "feature" of the enum. Or just use function directly ```d char[] DEFAULT() pure { return "hello".dup; } void bar(char[] data = DEFAULT) { data[0] = '1'; DEFAULT[0] = '2'; writeln(data); writeln(DEFAULT); } // works too void baz(string data = DEFAULT) {} ```
Jun 06
prev sibling next sibling parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 6/6/21 11:05 AM, Mathias LANG wrote:
 On Sunday, 6 June 2021 at 14:05:54 UTC, Jack Applegame wrote:
 On Sunday, 6 June 2021 at 13:21:35 UTC, Mathias LANG wrote:
 It *might*, e.g. the following:
 ```D
 enum DEFAULT = foo();
 char[] foo () { return null; }
 void bar (char[] arg = DEFAULT);
 ```
Mutable enum??? WAT? It's absolutely unacceptable. ```d char[] foo() safe { return "hello".dup; } enum WAT = foo(); void main() safe {     WAT[0] = 'w';     writeln(WAT); } ``` ```shell Error: program killed by signal 11 ```
This is... interesting. I did a bit of digging in memory and the code was actually using `int[]` (or at least, not `char[]`), so it was something like this: ```D import std.stdio; int[] foo() safe { return [42, 42, 42]; } enum WAT = foo(); void main() safe {     bar(); } void bar(int[] data = WAT) safe {     data[0] = 84;     writeln(data);     writeln(WAT); } ``` The above works as expected (at least, as *I* would expect). However, change the type to `char[]` and it SEGV. That's probably because string literals are special and the compiler makes some (bad) assumptions.
Yeah, I can get it to work if I use an array literal of characters: ```d enum char[] WAT = [42, 42, 42]; // or char[] foo() safe { return [42, 42, 42]; } enum WAT = foo(); ``` but if you use .dup on it, then it treats it like a string literal, e.g. instead of allocating a new array, it points at the same array (apparently in the RO segment) This doesn't happen with an integer array (it's always reallocated on every call). Seems like this behavior started with 2.066. -Steve
Jun 07
prev sibling parent Q. Schroll <qs.il.paperinik gmail.com> writes:
On Sunday, 6 June 2021 at 15:05:47 UTC, Mathias LANG wrote:
 Mutable enum???
 It's absolutely unacceptable.

 ```d
     WAT[0] = 'w';
 }
 ```
However, the compiler shouldn't allow you to use an enum as an lvalue.
It doesn't, at least when taking the statement by word. The thing being an lvalue here is `WAT[0]`, not `WAT`. It gets clearer when a function `f` returns an array (by value, of course); then, `f()[0]` is an lvalue, despite `f()` not being one. Dynamic array elements always have an address, they're always lvalues. The statement `[1, 2, 3][1] = 2;` type-checks, compiles (as it should, albeit nonsensical) -- and considering that enums are named literals, `WAT[1] = 2;` isn't much different. There's nothing stopping you from typing an enum `immutable`: ```D enum immutable(int[]) WAT = [1, 2, 3]; ``` Will refuse to have its elements assigned by virtue of `immutable`.
Jul 08
prev sibling next sibling parent Jack Applegame <japplegame gmail.com> writes:
On Sunday, 6 June 2021 at 10:47:54 UTC, Mathias LANG wrote:
 I wouldn't go that far. `enum` are like `#define`, and there 
 *are* cases where you want them. E.g. when using them as define 
 arguments:
 ```D
 void foo (char[] fmt = DEFAULT_VALUE) { /* ... */ }
 ```

 If `DEFAULT_VALUE` is `immutable`, this will never compile, but 
 with `enum`, it might.
Both compiles perfectly: ```d const DEFAULT_VALUE = "aaaa"; void foo (const char[] fmt = DEFAULT_VALUE) {} const DEFAULT_VALUE = "aaaa"; void foo (char[] fmt = DEFAULT_VALUE.dup) {} ```
Jun 06
prev sibling parent Claude <claudemr live.fr> writes:
On Sunday, 6 June 2021 at 10:47:54 UTC, Mathias LANG wrote:
 On Sunday, 6 June 2021 at 10:40:13 UTC, Jack Applegame wrote:
 That's not enough. Let's deprecate any use of enum as a 
 manifest constant.
I wouldn't go that far. `enum` are like `#define`, and there *are* cases where you want them. E.g. when using them as define arguments: ```D void foo (char[] fmt = DEFAULT_VALUE) { /* ... */ } ```
Yes, and if the memory layout is like in C, I would expect 'immutable' variables to be addressable (in RO data segment), but not 'enum' constants (which would be simple rvalues, probably directly used as-is in assembly instructions).
Jun 07