www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Feature request: enum init shouldn't create a new enumeration

reply "Tommi" <tommitissari hotmail.com> writes:
I'd like to be able to specify a default value for a named enum, 
E.init, without creating a new enumeration. There are three 
reasons:
1) Default initializing enum variables to an "invalid" value
2) Being able to use 'final switch' without the 'init' case
3) "Invalid" init value wouldn't affect E.min or E.max

Here's what currently happens:

enum MyEnum
{
     init = -123,
     first = 0,
     second = 1
}

void main()
{
     static assert(MyEnum.min == -123);

     MyEnum me;

     final switch (me)
     {
     case MyEnum.first:  break;
     case MyEnum.second: break;
     case MyEnum.init: // I'm forced to specify init case too
     }
}

This is what I'd like to happen:

enum MyEnum
{
     init = -123,
     first = 0,
     second = 1
}

void main()
{
     static assert(MyEnum.min == 0); // no effect on min/max

     MyEnum me;

     final switch (me) // no init case necessary nor allowed
     {
     case MyEnum.first:  break;
     case MyEnum.second: break;
     }
}
Oct 13 2012
next sibling parent reply "denizzzka" <4denizzz gmail.com> writes:
On Saturday, 13 October 2012 at 15:39:24 UTC, Tommi wrote:
 enum MyEnum
 {
     init = -123,
     first = 0,
     second = 1
 }

 void main()
 {
     static assert(MyEnum.min == -123);

     MyEnum me;

     final switch (me)
     {
     case MyEnum.first:  break;
     case MyEnum.second: break;
     case MyEnum.init: // I'm forced to specify init case too
     }
 }

Also, a quick question: Why in case its need to write name of the enum? case first: break; case second: break; case init: // I'm forced to specify init case too looks better for me.
Oct 13 2012
parent =?UTF-8?B?QWxpIMOHZWhyZWxp?= <acehreli yahoo.com> writes:
On 10/13/2012 11:01 AM, bearophile wrote:
 denizzzka:

 Why in case its need to write name of the enum?

Because D enums have a very simple design. But with() helps.

For me, that is the only benefit of 'with': final switch (me) with (MyEnum) { case first: break; case second: break; case init: break; } The other uses of 'with' are more like obfuscations.
 Bye,
 bearophile

Ali
Oct 13 2012
prev sibling next sibling parent "bearophile" <bearophileHUGS lycos.com> writes:
denizzzka:

 Why in case its need to write name of the enum?

Because D enums have a very simple design. But with() helps. Bye, bearophile
Oct 13 2012
prev sibling next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Saturday, October 13, 2012 17:39:23 Tommi wrote:
 I'd like to be able to specify a default value for a named enum,
 E.init, without creating a new enumeration. There are three
 reasons:
 1) Default initializing enum variables to an "invalid" value
 2) Being able to use 'final switch' without the 'init' case
 3) "Invalid" init value wouldn't affect E.min or E.max
 
 Here's what currently happens:
 
 enum MyEnum
 {
      init = -123,
      first = 0,
      second = 1
 }
 
 void main()
 {
      static assert(MyEnum.min == -123);
 
      MyEnum me;
 
      final switch (me)
      {
      case MyEnum.first:  break;
      case MyEnum.second: break;
      case MyEnum.init: // I'm forced to specify init case too
      }
 }
 
 This is what I'd like to happen:
 
 enum MyEnum
 {
      init = -123,
      first = 0,
      second = 1
 }
 
 void main()
 {
      static assert(MyEnum.min == 0); // no effect on min/max
 
      MyEnum me;
 
      final switch (me) // no init case necessary nor allowed
      {
      case MyEnum.first:  break;
      case MyEnum.second: break;
      }
 }

Think about that for a moment. What happens when that final switch statement is actually run? Which statement would MyEnum.init use? The whole point of final switch is that the compile _knows_ that every single value for that type has a case. With your suggestion, it specifically _doesn't_ have a case for one of the type's values. And it _will_ happen at some point that you'll hit a switch like that with the init value rather than a valid one. I don't see how that can possibly work or make any sense at all. And remember, that in many cases, T.init is considered to be perfectly valid. By allowing a final switch _not_ to have it, it then becomes easy to forget to add a case for it when you _need_ to, completely defeating the purpose of the final switch (to make it so that both you and the compiler know that all of the possible values are accounted for). - Jonathan M Davis
Oct 13 2012
parent "Daniel Murphy" <yebblies nospamgmail.com> writes:
https://github.com/D-Programming-Language/dmd/pull/673

"Tommi" <tommitissari hotmail.com> wrote in message 
news:ozvdphftlwxkeumrnvoq forum.dlang.org...
 On Monday, 15 October 2012 at 08:25:18 UTC, Tommi wrote:
 in reality, it's very easy to write a bug that makes an enum variable 
 have an invalid value. E.g:

Writing that bug wasn't as easy as I thought though. Here's the code with a bug: enum MyEnum { first, second, third } auto me = MyEnum.min; while (me <= MyEnum.max) { // do something ++me; } final switch (me) // this should throw { case MyEnum.first: break; case MyEnum.second: break; case MyEnum.third: break; } Instead, currently this code runs just fine, none of the switch cases are taken. So, this bug might manifest itself very far from where it actually should have been throwing: on that final switch expression. Thus hunting this bug down could be tedious.

Oct 17 2012
prev sibling next sibling parent "Tommi" <tommitissari hotmail.com> writes:
On Saturday, 13 October 2012 at 20:25:56 UTC, Jonathan M Davis 
wrote:
 
      MyEnum me;
 
      final switch (me) // no init case necessary nor allowed
      {
      case MyEnum.first:  break;
      case MyEnum.second: break;
      }
 }

Think about that for a moment. What happens when that final switch statement is actually run?

There's a bug in that code, because MyEnum default-initializes to an invalid value. It's effectively the same as this following code, where the programmer has failed to initialize MyEnum variable with a valid value: enum MyEnum { first, second } void main() { MyEnum me = cast(MyEnum)(-123); final switch (me) { case MyEnum.first: break; case MyEnum.second: break; } } I think that the final switch statement above should throw an unrecoverable error. I tested it, and nothing happens. I strongly disagree with this behavior of the compiler (a bug perhaps?).
 And remember, that in many cases, T.init is considered to be 
 perfectly valid. By allowing a final switch _not_ to have it,
 it then becomes easy to forget to add a case for it when you
 _need_ to, completely defeating the purpose of the final
 switch (to make it so that both you and the compiler know that 
 all of the possible values are accounted for).

If T.init is considered to be perfectly valid, then it means, that a synonym for it exists among the enumerations. E.g: enum MyEnum { init = 1, first = 1, second = 42 } enum ThyEnum { first, second } In both of those cases, T.first is the synonym of T.init, which is what both of those enums default-initialize to. Therefore, if T.init is a valid value, and thus has a synonym among the enumerations of T, then you can't add a case for init in a final switch: MyEnum me; final switch (me) { MyEnum.first: break; MyEnum.second: break; MyEnum.init: // Error: duplicate case cast(MyEnum)1 // in switch statement } So, this situation you describe, where you *need* to add a case for init in a final switch, it doesn't exist. T.init should always either 1) have a synonym among the enumerations or 2) represent an invalid value.
Oct 13 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday, October 14, 2012 08:20:40 Tommi wrote:
 There's a bug in that code, because MyEnum default-initializes to
 an invalid value. It's effectively the same as this following
 code, where the programmer has failed to initialize MyEnum
 variable with a valid value:

Valid or not, MyEnum.init is still a value for MyEnum and _must_ be accounted for. Trying to treat MyEnum.init as not being a value of MyEnum is likely to break all kinds of stuff. Being able to have a variable of an enum type with a value which is not really a member of the enum is just plain broken. And honestly, declaring a specific init value for an enum is a stupid idea. It's going to screw with all kinds of stuff. Anything assuming that the init property is the first value (is it is in _all_ other cases but isn't necessarily if you declare your own) will be broken. It's still going to end up being in stuff like std.traits.EnumMembers or pretty much anything which operates on enums unless all kinds of special casing is added. TDPL does mention (p. 275) that you can declare enum members with the names max, min, and init, but it also points out that it's a dumb idea. I'd argue that it shouldn't even be legal at all. It's just begging for trouble. - Jonathan M Davis
Oct 13 2012
prev sibling next sibling parent "Tommi" <tommitissari hotmail.com> writes:
On Sunday, 14 October 2012 at 06:51:48 UTC, Jonathan M Davis 
wrote:
 And honestly, declaring a specific init value for an enum is a 
 stupid idea.

I think that declaring a specific *valid* init value for an enum has no purpose. But declaring a specific *invalid* init value, to which the enum initializes to by default, is a very good idea. The reason for why it's a good idea, is exactly the same as why default-initializing floating point variables to NaN is a good idea. Others have reasoned about that enough, thus I don't have to. On Sunday, 14 October 2012 at 06:51:48 UTC, Jonathan M Davis wrote:
 ... It's going to screw with all kinds of stuff.

True, it would break code.
Oct 14 2012
prev sibling next sibling parent Nick Sabalausky <SeeWebsiteToContactMe semitwist.com> writes:
On Sun, 14 Oct 2012 09:16:28 +0200
"Tommi" <tommitissari hotmail.com> wrote:

 On Sunday, 14 October 2012 at 06:51:48 UTC, Jonathan M Davis 
 wrote:
 And honestly, declaring a specific init value for an enum is a 
 stupid idea.

I think that declaring a specific *valid* init value for an enum has no purpose. But declaring a specific *invalid* init value, to which the enum initializes to by default, is a very good idea. The reason for why it's a good idea, is exactly the same as why default-initializing floating point variables to NaN is a good idea. Others have reasoned about that enough, thus I don't have to.

Yes, but it still has to be taken into account in things like "final switch". You can't just pretend that nothing will ever have that value, because by making it the init value, you've *made* it an actual possible value, one that just happens to indicate "uninitialized".
Oct 14 2012
prev sibling next sibling parent "Tommi" <tommitissari hotmail.com> writes:
On Sunday, 14 October 2012 at 19:40:17 UTC, Nick Sabalausky wrote:
 Yes, but it still has to be taken into account in things like 
 "final switch". You can't just pretend that nothing will ever
 have that value, because by making it the init value, you've
 *made* it an actual possible value, one that just happens to
 indicate "uninitialized".

*I* know that named enum variables can have whatever values; it's rather *you* and DMD who are pretending that enum variables can have only those values which are specifically enumerated. You say that final switch should take into account an enum init value which can be used to represent an invalid value. I say that final switch shouldn't consider that (invalid) init value any different from all the other values that are invalid for that specific enum type, that is: all the values that are not speficied explicitly by the enumerations. And the way final switch should take all those invalid values into account, is by throwing an error when a final switch switches on a value not specifically defined by the enumerations. Dmd also seems blinded into thinking that the specified enumerations are all that an enum variable can ever be, while in reality, it's very easy to write a bug that makes an enum variable have an invalid value. E.g: enum MyEnum { first, second, third } auto me = MyEnum.min; while (me < MyEnum.max) { // do something ++me; } switch (me) // this should throw { case MyEnum.first: break; case MyEnum.second: break; case MyEnum.thrird: break; }
Oct 15 2012
prev sibling parent "Tommi" <tommitissari hotmail.com> writes:
On Monday, 15 October 2012 at 08:25:18 UTC, Tommi wrote:
 in reality, it's very easy to write a bug that makes an enum 
 variable have an invalid value. E.g:

Writing that bug wasn't as easy as I thought though. Here's the code with a bug: enum MyEnum { first, second, third } auto me = MyEnum.min; while (me <= MyEnum.max) { // do something ++me; } final switch (me) // this should throw { case MyEnum.first: break; case MyEnum.second: break; case MyEnum.third: break; } Instead, currently this code runs just fine, none of the switch cases are taken. So, this bug might manifest itself very far from where it actually should have been throwing: on that final switch expression. Thus hunting this bug down could be tedious.
Oct 15 2012