Programming in D – Tutorial and Reference
Ali Çehreli

Other D Resources

enum

enum is the feature that enables defining named constant values.

Effects of magic constants on code quality

The following code appeared in the exercise solutions of the Integers and Arithmetic Operations chapter:

        if (operation == 1) {
            result = first + second;

        } else if (operation == 2) {
            result = first - second;

        } else if (operation == 3) {
            result = first * second;

        } else if (operation == 4) {
            result = first / second;
        }

The integer literals 1, 2, 3, and 4 in that piece of code are called magic constants. It is not easy to determine what each of those literals means in the program. One must examine the code in each scope to determine that 1 means addition, 2 means subtraction, etc. This task is relatively easy for the code above because all of the scopes contain just a single line. It would be considerably more difficult to decipher the meanings of magic constants in most other programs.

Magic constants must be avoided because they impair two important qualities of source code: readability and maintainability.

enum enables giving names to such constants and, as a consequence, making the code more readable and maintainable. Each condition would be readily understandable if the following enum constants were used:

        if (operation == Operation.add) {
            result = first + second;

        } else if (operation == Operation.subtract) {
            result = first - second;

        } else if (operation == Operation.multiply) {
            result = first * second;

        } else if (operation == Operation.divide) {
            result = first / second;
        }

The enum type Operation above that obviates the need for magic constants 1, 2, 3, and 4 can be defined like this:

    enum Operation { add = 1, subtract, multiply, divide }
The enum syntax

The common definition of an enum is the following:

    enum TypeName { ValueName_1, ValueName_2, /* etc. */ }

Sometimes it is necessary to specify the actual type (the base type) of the values as well:

    enum TypeName : base_type { ValueName_1, ValueName_2, /* etc. */ }

We will see how this is used in the next section.

TypeName defines what the constants collectively mean. All of the member constants of an enum type are listed within curly brackets. Here are some examples:

    enum HeadsOrTails { heads, tails }
    enum Suit { spades, hearts, diamonds, clubs }
    enum Fare { regular, child, student, senior }

Each set of constants becomes part of a separate type. For example, heads and tails become members of the type HeadsOrTails. The new type can be used like other fundamental types when defining variables:

    HeadsOrTails result;           // default initialized
    auto ht = HeadsOrTails.heads;  // inferred type

As has been seen in the pieces of code above, the members of enum types are always specified by the name of their enum type:

    if (result == HeadsOrTails.heads) {
        // ...
    }
Actual values and base types

The member constants of enum types are by default implemented as int values. In other words, although on the surface they appear as just names such as heads and tails, they also have numerical values. (Note: It is possible to choose a type other than int when needed.).

Unless explicitly specified by the programmer, the numerical values of enum members start at 0 and are incremented by one for each member. For example, the two members of the HeadsOrTails enum have the numerical values 0 and 1:

    writeln("heads is 0: ", (HeadsOrTails.heads == 0));
    writeln("tails is 1: ", (HeadsOrTails.tails == 1));

The output:

heads is 0: true
tails is 1: true

It is possible to manually (re)set the values at any point in the enum. That was the case when we specified the value of Operation.add as 1 above. The following example manually sets a new count twice:

    enum Test { a, b, c, ç = 100, d, e, f = 222, g, ğ }
    writefln("%d %d %d", Test.b, Test.ç, Test.ğ);

The output:

1 100 224

If int is not suitable as the base type of the enum values, the base type can be specified explicitly after the name of the enum:

    enum NaturalConstant : double { pi = 3.14, e = 2.72 }
    enum TemperatureUnit : string { C = "Celsius", F = "Fahrenheit" }
enum values that are not of an enum type

We have discussed that it is important to avoid magic constants and instead to take advantage of the enum feature.

However, sometimes it may not be natural to come up with enum type names just to use named constants. Let's assume that a named constant is needed to represent the number of seconds per day. It should not be necessary to also define an enum type to contain this constant value. All that is needed is a constant value that can be referred to by its name. In such cases, the type name of the enum and the curly brackets are omitted:

    enum secondsPerDay = 60 * 60 * 24;

The type of the constant can be specified explicitly, which would be required if the type cannot be inferred from the right hand side:

    enum int secondsPerDay = 60 * 60 * 24;

Since there is no enum type to refer to, such named constants can be used in code simply by their names:

    totalSeconds = totalDays * secondsPerDay;

enum can be used for defining named constants of other types as well. For example, the type of the following constant would be string:

    enum fileName = "list.txt";

Such constants are rvalues and they are called manifest constants.

It is possible to create manifest constants of arrays and associative arrays as well. However, as we will see later in the Immutability chapter, enum arrays and associative arrays may have hidden costs.

Properties

The .min and .max properties are the minimum and maximum values of an enum type. When the values of the enum type are consecutive, they can be iterated over in a for loop within these limits:

    enum Suit { spades, hearts, diamonds, clubs }

    for (auto suit = Suit.min; suit <= Suit.max; ++suit) {
        writefln("%s: %d", suit, suit);
    }

Format specifiers "%s" and "%d" produce different outputs:

spades: 0
hearts: 1
diamonds: 2
clubs: 3

Note that a foreach loop over that range would leave the .max value out of the iteration:

    foreach (suit; Suit.min .. Suit.max) {
        writefln("%s: %d", suit, suit);
    }

The output:

spades: 0
hearts: 1
diamonds: 2
               ← clubs is missing

For that reason, a correct way of iterating over all values of an enum is using the EnumMembers template from the std.traits module:

import std.traits;
// ...
    foreach (suit; EnumMembers!Suit) {
        writefln("%s: %d", suit, suit);
    }

Note: The ! character above is for template instantiations, which will be covered in a later chapter.

spades: 0
hearts: 1
diamonds: 2
clubs: 3       ← clubs is present
Converting from the base type

As we saw in the formatted outputs above, an enum value can automatically be converted to its base type (e.g. to int). The reverse conversion is not automatic:

    Suit suit = 1;       // ← compilation ERROR

One reason for this is to avoid ending up with invalid enum values:

    suit = 100;          // ← would be an invalid enum value

The values that are known to correspond to valid enum values of a particular enum type can still be converted to that type by an explicit type cast:

    suit = cast(Suit)1;  // now hearts

It would be the programmer's responsibility to ensure the validity of the values when an explicit cast is used. We will see type casting in a later chapter.

Exercise

Modify the calculator program from the exercises of the Integers and Arithmetic Operations chapter to have the user select the arithmetic operation from a menu.

This program should be different from the previous one in at least the following areas: