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.
Permalink
Reply