Faux Pas

Exhaustive Enum Switch Statements

Ali Rantakari
April 16, 2015

Version 1.4 of Faux Pas introduced a new rule: Unnecessary default case in exhaustive switch statement (DefaultInExhaustiveSwitch). The purpose of this rule is to find code that prevents a useful compiler warning from triggering. In order to explain this, let’s look at an example.

Let’s say we have an enum of vehicle types:

typedef NS_ENUM(NSUInteger, FOOVehicleType) {
    FOOVehicleTypeCar,
    FOOVehicleTypeBike,
    FOOVehicleTypeBoat
};

We’d then like to write a simple function hasWheels which returns whether a given vehicle type has wheels. There are a couple of ways to write this. For example:

// Implementation #1
BOOL hasWheels(FOOVehicleType type) {
    return (type == FOOVehicleTypeCar || type == FOOVehicleTypeBike);
}

or:

// Implementation #2
BOOL hasWheels(FOOVehicleType type) {
    return (type != FOOVehicleTypeBoat);
}

or:

// Implementation #3
BOOL hasWheels(FOOVehicleType type) {
    switch(type) {
        case FOOVehicleTypeCar:
        case FOOVehicleTypeBike:
            return YES;
        case FOOVehicleTypeBoat:
        default:
            return NO;
    }
}

All of the above implementations are correct, but another thing they all have in common is that none of them allow the compiler to help you when new fields are added to the enum.

For example, if the field FOOVehicleTypeTruck was added, then implementations #1 and #3 would become incorrect (returning NO for this new value). Similarly if the field FOOVehicleTypeAirplane was added, then implementation #2 would become incorrect (returning YES for this new value).

In these cases, whoever adds the new field to the enum would have to remember to find, review, and update all routines like this that make decisions based on values of this enum type. This can be a tall order, especially with larger codebases.

Now let’s look at a better way to write this function:

// Implementation #4 (recommended)
BOOL hasWheels(FOOVehicleType type) {
    switch(type) {
        case FOOVehicleTypeCar:
        case FOOVehicleTypeBike:
            return YES;
        case FOOVehicleTypeBoat:
            return NO;
    }
}

Notice how this is exactly the same as implementation #3 above, except that this one has no default case.

The default case is not needed, because this switch statement is exhaustive (i.e. it handles all possible values.) This is also why the compiler won’t warn about code paths that don’t explicitly return a value.

The best thing about omitting the default case, however, is the fact that now the compiler can give you warnings when new fields are added to the enum:

test.m:12:12: warning: enumeration value 'FOOVehicleTypeAirplane' not handled in switch [-Wswitch]
    switch(type) {
           ^

This is extremely useful: just add the new field to the enum declaration, recompile the project, and the compiler will point out the places where you have to now take this new field into account.

This compiler warning, -Wswitch, is enabled by default in new Xcode projects (and by -Wall). In order to get these warnings even when default cases are present, enable the -Wswitch-enum warning — this is exactly the same as -Wswitch but warns even when a default case is used.

In many cases, -Wswitch-enum may be too invasive, though, because it forces you to make all of your enum switch statements exhaustive. In cases like this, the DefaultInExhaustiveSwitch rule in Faux Pas becomes quite helpful.

Faux Pas does not presume to warn about implementations #1 and #2 above (the false positive rate for such warnings would be intolerable) but it does warn about implementation #3 — exhaustive switch statements that use a default case. Hopefully this rule will help you update your code to better take advantage of these useful compiler warnings.