www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Strongly typed enums

reply bearophile <bearophileHUGS lycos.com> writes:
I have discussed a bit this topic probably one year ago, but it's good to talk
some more about it.

Jane Street is a known trading firm, they use the OCaML language because it's
efficient and because its type safety, it is a very statically typed language.

For them one of the simple but important qualities of OcaML (and some other
functional language) are the "match" statement (for pattern matching) because
it statically forbids missed cases and redundant cases. This is very useful
because if you add one more case in an enumerated type, the compiler will
produce an error for all the match present in your whole program. This makes
much more safe to modify code.

They talk about it here, from 33 minutes, it's an interesting video on the
whole:
http://vimeo.com/14317442

I presume Walter has added the "final switch" to D for the same purpose: if you
add or remove one element from an enum, final switches will give you errors at
compile time. This removes a common source of bugs in C code.

Recently I have created a bug caused (not caught) by the nature of D enums.
This is just the last one of similar bugs.

The following is reduced code of a small game. The main contains a while that
loops until the game is finished.

The original version of this program was simpler, and instead of using the
GameState enum, it just used 0, 1 and -1 constants in the code. So the original
version of isFinished tests if winner() != -1.

I have added the enum GameState, now winner() returns. But I have forgotten to
update the isFinished() function too. The D language doesn't catch that simple
bug:


enum GameState { inProgress, draw, humanWins, computerWins }

GameState winner() {
    // this function used to return -1, 1, 0 values
    // ...
}
bool isFinished() {
    return winner() != -1; // not updated function!
    //return winner() != GameState.inProgress; // correct code!
}
void main() {
    while (!isFinished()) {
        // ...
    }
    // ...
}


In a bigger program it becomes less easy to catch a similar bug (this bug was
not found also because of another waeak typing characteristic of D language:
inside isFinished it allowes you to compare an unsigned size_t value with -1,
despite -1 is statically visibly outside the range of possible unsigned values).

If I write similar code in C++11, it catches that bug:


enum class GameState {
    inProgress,
    draw,
    humanWins,
    computerWins
};
GameState winner() {
    return GameState::draw;
}
bool isFinished() {
    return winner() != -1; // line 11, error
}
int main() {}


G++ 4.6.0 outputs:

test.cpp: In function 'bool isFinished()':
test.cpp:11:25: error: no match for 'operator!=' in 'winner() != -0x000000001'


In D final switches where introduced to avoid essentially the same class of
bugs.

So I think I'd like named D enums to be strongly typed. Weak typing is better
left to old versions of the C language.

More info in my enhancement request:
http://d.puremagic.com/issues/show_bug.cgi?id=3999


Do you know what are the disadvantages of this change in D enums? Do you know
if it is going to cause other bugs or problems?

If you really want to keep weakly typed enums in D then I am able to invent a
new D syntax to specify strongly typed enums, like this, but I suggest to not
go this way:

typedef enum GameState { inProgress, draw, humanWins, computerWins }

Bye,
bearophile
Aug 25 2011
parent reply Adam Ruppe <destructionator gmail.com> writes:
I think I'm for this. If you provide a name with no base type,
it should work like this.

Bearophile, have you considered making a pull request? I doubt it
would be too hard in the compiler, and then we can try it to see
what code breaks.

I imagine if any code breaks, it'd be easy enough to fix with
changing to the item or inserting a cast.
Aug 25 2011
parent reply bearophile <bearophileHUGS lycos.com> writes:
Adam Ruppe:

 I think I'm for this. If you provide a name with no base type,
 it should work like this.

Why do you suggest to disallow implicit conversions (and use strong typing) only if the enum definition doesn't specify a base type?
 Bearophile, have you considered making a pull request?

I think I am not able to do that yet.
 I imagine if any code breaks, it'd be easy enough to fix with
 changing to the item or inserting a cast.

Recently I have discussed safe enum conversions too. In some cases it's good to replace cast() with a to!. http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.D&article_id=142862 Bye, bearophile
Aug 25 2011
parent Adam D. Ruppe <destructionator gmail.com> writes:
bearophile wrote:
 Why do you suggest to disallow implicit conversions (and use strong
 typing) only if the enum definition doesn't specify a base type?

With a base type, it looks like inheritance, so I expect some implicit conversions are allowed. class A : Base {} Base a = new A(); // allowed enum ENUM : int {} int a = ENUM.something; // same deal? i think so
 I think I am not able to do that yet.

You should try - the compiler isn't too hard, and github isn't bad once you've done it once. So then you'll be able to try these proposed features and it's easier to get a patch accepted than to hope Walter does it all.
  In some cases it's good to replace cast() with a to!.

I use to a lot with enums. It's really convenient how it goes to and from string based on the member name. I don't think I've ever actually tried to! with int!
Aug 25 2011