www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - enum confusion

reply Don Allen <donaldcallen gmail.com> writes:
I've posted here before and then took a D hiatus. I'm working on 
the pre-hiatus project again, which involves porting about 9000 
lines of working but ugly C I wrote 10 years ago, implementing a 
suite of personal financial management software.

Programming languages are an area of interest of mine and have 
been for a long time. In the years since I started writing code 
(over 60 years ago!) professionally and otherwise, I've used just 
about every language you can think of and some you can't. I 
considered a number of languages for this project (e.g, Rust, 
Scheme, Haskell, Nim, Zig) and rejected all for various reasons. 
So far, I've been mostly happy with my choice of D and much of 
the work is done, with the core functionality working. The code 
is more concise and readable than the C it came from. Performance 
is absolutely fine. I love the fast compile times (dmd on a 
FreeBSD system) and the ability to debug with gdb.

But ... you knew this was coming ... I find enums and/or their 
documentation to be a weak spot. As time permits, I'll have more 
to say about this, but I want to raise an initial issue here for 
comments.

The Language Reference has three subsections on enums -- Named 
Enums, Anonymous Enums and Manifest Constants. The first mention 
that I can find of when enums are evaluated and how/where they 
are stored occurs in the Manifest Constants subsection (17.3): 
"Manifest constants are not lvalues, meaning their address cannot 
be taken. They exist only in the memory of the compiler." This 
can be read to suggest that the other two types of enums 
described *are* lvalues, though the documentation doesn't say one 
way or the other. Finding this hard to believe, I wrote a little 
test code:

````
   1 import std.stdio;
   2
   3 enum Foo {
   4     bar
   5 }
   6
   7 int main(string[] args)
   8 {
   9     writefln("debug: %d\n", &(Foo.bar));
  10     return 0;
  11 }
````

Compiling results in

test1.d(9): Error: manifest constant `bar` cannot be modified

Foo.bar is *not* a manifest constant according to the Language 
Ref; it's a Named Enum as described in 17.1. 17.3 defines 
manifest constants as a special case of Anonymous Enums, those 
having a single member. But if you add another member to the 
Named Enum in my test:

````
   1 import std.stdio;
   2
   3 enum Foo {
   4     bar,
   5     baz
   6 }
   7
   8 int main(string[] args)
   9 {
  10     writefln("debug: %d\n", &(Foo.bar));
  11     return 0;
  12 }
````

making a Named Enum with multiple members -- certainly not what 
the documentation is calling a manifest constant -- and yet you 
get the same error message from the compiler, referring to 'bar' 
as a manifest constant.

This suggests to me that the compiler is doing what I expect it 
would and should do -- evaluating all enums at compile time, none 
of them lvalues -- whether named, anonymous or what the 
documentation calls manifest constants. It would appear that the 
only difference among the various enum flavors is whether a new 
type is created or not (named vs anonymous) and a little 
syntactic sugar allowing the omission of braces for single-member 
anonymous enums. There may be additional differences in how the 
various cases are treated by to!string that I can't discuss right 
now because I haven't looked at this carefully enough.

So my conclusion is that there's a disconnect between the 
Language Reference and what the compiler is actually doing and I 
think the problem is that documentation is incorrect, or 
misleading at best.

I'm happy to hear the observations of people who know this 
language better than I do. Perhaps I'm missing something here.
May 08 2022
next sibling parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 5/8/22 18:50, Don Allen wrote:

 I'm working on the
 pre-hiatus project again
Welcome back!
 So far, I've been mostly happy with my choice of D
 and much of the work is done, with the core functionality working. The
 code is more concise and readable than the C it came from. Performance
 is absolutely fine. I love the fast compile times (dmd on a FreeBSD
 system) and the ability to debug with gdb.
I am feeling panicky as DConf submission deadline is approaching (May 15, 2022). I would love to watch your report on this project.
 Named Enums,
Check.
 Anonymous Enums
What a strange feature. :)
 and Manifest Constants.
Check.
 "Manifest constants are not
 lvalues, meaning their address cannot be taken. They exist only in the
 memory of the compiler." This can be read to suggest that the other two
 types of enums described *are* lvalues
Yes, unclear wording but no, none of them are lvalues. Equivalent constructs that I can think of are 'static const' and 'static immutable'. Those would be evaluated at compile time but have addresses: static const a = foo(); static immutable b = bar(); int foo() { return 42; } int bar() { return 43; } void main() { assert(&a != &b); }
 evaluating all enums at compile time, none of them
 lvalues
YES! :) Ali P.S. While looking for traits related to rvalues and lvalues, I realized that at least isRvalueAssignable() and isLvalueAssignable() do not appear at the contents section on this page: https://dlang.org/phobos/std_traits.html There are issues with Phobos documentation. :/
May 08 2022
parent reply Don Allen <donaldcallen gmail.com> writes:
Ali --

Thanks for the helpful comments. I hope these documentation 
issues get fixed. I'll submit an issue on github and if I can 
find the time, will submit a pull request to fix the enum 
discussion in the Language Reference.

Speaking of documentation, your book has been invaluable as I've 
made my way through this project. It really is excellent.

/Don
May 10 2022
parent =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 5/10/22 05:16, Don Allen wrote:

 I'll submit an issue on github and if I can find the time, will
 submit a pull request to fix the enum discussion in the Language 
Reference. Much appreciated! :)
 Speaking of documentation, your book has been invaluable as I've made my
 way through this project. It really is excellent.
Thank you! That means a lot to me. Ali
May 11 2022
prev sibling next sibling parent Salih Dincer <salihdb hotmail.com> writes:
On Monday, 9 May 2022 at 01:50:44 UTC, Don Allen wrote:
 [...]
 Foo.bar is *not* a manifest constant according to the Language 
 Ref; it's a Named Enum as described in 17.1. 17.3 defines 
 manifest constants as a special case of Anonymous Enums, those 
 having a single member. But if you add another member to the 
 Named Enum in my test:

 ````
   1 import std.stdio;
   2
   3 enum Foo {
   4     bar,
   5     baz
   6 }
   7
   8 int main(string[] args)
   9 {
  10     writefln("debug: %d\n", &(Foo.bar));
  11     return 0;
  12 }
 ````

 making a Named Enum with multiple members -- certainly not what 
 the documentation is calling a manifest constant -- and yet you 
 get the same error message from the compiler, referring to 
 'bar' as a manifest constant.
Parentheses are there to make things easier for me. Same for the compiler. That is to avoid confusion or misunderstanding. We get the same result in the 8 examples below. ```d struct Foo {  int bar;  alias bar this;  auto far() { return &bar; } } void main() {  Foo foo;  // to direct  "1: ".writeln(&foo.bar); (&foo.bar).writefln!"2. %s";  "3: ".writeln(&(foo.bar));  (&(foo.bar)).writefln!"4. %s";  // by alias  "5. ".writeln(&foo);  (&foo).writefln!"6. %s";  // by function  "7: ".writeln(&(*foo.far));  (&(*foo.far)).writefln!"8. %s"; } ``` We do similar things for a mathematical operation, or we avoid parentheses if they comply with arithmetic precedence rules. In summary, for us humans, sometimes the definitions are different, but in machine language everything is the same. SDB 79
May 10 2022
prev sibling parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 5/8/22 9:50 PM, Don Allen wrote:

 But ... you knew this was coming ... I find enums and/or their 
 documentation to be a weak spot. As time permits, I'll have more to say 
 about this, but I want to raise an initial issue here for comments.
There are 2 types of enum meaning -- one is like #define, and declares there's some value that only exists at compile-time. That's the manifest constant. That is done by using enum kind of as a storage class (like const). ```d enum x = 5; // x is now a manifest constant of type int. enum byte y = 5; // y is now a manifest constant of type byte. writeln(&x); // error, not an lvalue, this is equivalent to: writeln(&5); ``` The second type of enum meaning is to *declare a type*. This type can be an lvalue: ```d enum Foo { bar } Foo foo; writeln(&foo); // ok ``` The symbol Foo now becomes a type, and can be used to denote an integer value that has only one value (Foo.bar). Though technically, it can be any int value via casting or math operations. You can also specify a base type if desired. Some libraries use this as a crude typedef (with extra manifest constant properties). An anonymous enum is a weird way of declaring a bunch of manifest constants, and use the "= last member + 1" feature for declaring them (if that's what you want). I don't think many people use them. Yes, I see that they are the same thing as manifest constants with one member, but I don't ever think of them that way. You can also declare an enum with an identifier, but without any members and becomes a type without a default initializer. Mostly usable as a handy UDA tag. ```d enum dontSerialize; struct S { dontSerialize string comment; } ``` enum is definitely one of the stranger things in D. -Steve
May 11 2022