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