digitalmars.D - Stop using the enum as a manifest constant.
- Jack Applegame (75/75) Jun 06 2021 Just don't do it.
- evilrat (4/6) Jun 06 2021 What did you expected from an enum array? It is known to allocate
- Jack Applegame (4/8) Jun 06 2021 It's a well-known "feature" of enum. And it's one of the reasons
- evilrat (3/12) Jun 06 2021 Nah, that enum arrays specifically, deprecating this specific
- Jack Applegame (27/29) Jun 06 2021 Except for character arrays. Just change `int[]` to
- Mathias LANG (15/17) Jun 06 2021 I wouldn't go that far. `enum` are like `#define`, and there
- Jack Applegame (8/16) Jun 06 2021 Really?
- Mathias LANG (7/24) Jun 06 2021 It *might*, e.g. the following:
- Jack Applegame (14/20) Jun 06 2021 Mutable enum??? WAT?
- Mathias LANG (25/45) Jun 06 2021 This is... interesting. I did a bit of digging in memory and the
- Jack Applegame (28/46) Jun 06 2021 It's definetly a code smell.
- Steven Schveighoffer (15/60) Jun 07 2021 Yeah, I can get it to work if I use an array literal of characters:
- Q. Schroll (15/24) Jul 08 2021 It doesn't, at least when taking the statement by word. The thing
- Jack Applegame (8/16) Jun 06 2021 Both compiles perfectly:
- Claude (5/15) Jun 07 2021 Yes, and if the memory layout is like in C, I would expect
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 2021
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 2021
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 2021
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:Nah, that enum arrays specifically, deprecating this specific case is probably ok though.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 2021
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 2021
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 2021
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 2021
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:It *might*, e.g. the following: ```D enum DEFAULT = foo(); char[] foo () { return null; } void bar (char[] arg = DEFAULT); ```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 2021
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 2021
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: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.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 2021
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 2021
On 6/6/21 11:05 AM, Mathias LANG wrote:On Sunday, 6 June 2021 at 14:05:54 UTC, Jack Applegame wrote: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. -SteveOn Sunday, 6 June 2021 at 13:21:35 UTC, Mathias LANG wrote: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.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 07 2021
On Sunday, 6 June 2021 at 15:05:47 UTC, Mathias LANG wrote: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`.Mutable enum??? It's absolutely unacceptable. ```d WAT[0] = 'w'; } ```However, the compiler shouldn't allow you to use an enum as an lvalue.
 Jul 08 2021
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 2021
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: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).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) { /* ... */ } ```
 Jun 07 2021








 
  
  
 
 Jack Applegame <japplegame gmail.com>
 Jack Applegame <japplegame gmail.com> 