I recently had someone new to C programming ask me on IRC when you should use an enum rather than a struct. Baffled by the nature of the question, I politely suggested more reading before posing the question again. After all, structs are so different from enums, right? My request was met with resistance, likely because there is something to be said for practice as well as reading to fully “grok” things like these.

It was now that I realised the similarity that a naïve programmer might see between a struct and an enum, and how this might cause confusion between the two types.

I hope for this article to serve as both a reflection for myself as well as a useful reference for anyone requiring further clarification in this area. I do not aim for it to be an introduction to structs and enums from the ground-up, but knowing my tendency to ramble, don’t be surprised it that’s the way it ends up.

The Similarity

As far as a newbie programmer is concerned, structs and enums both enclose some items as members inside some user-defined type. From a naïve perspective, they each look something like this:

(struct|enum) name {
    some
    collection
    of
    items
};

The Distinction

The primary difference to note is that structs are used for data storage whereas enums are used for labelling values.

You might like to compare a constant macro definition to a variable storage:

#define SOMEVALUE 5

void somefoo() {
    int a = 5;
}

Both of these record the value 5, except there is a difference in the way the information is stored in the program once it is compiled. Disregard any details such as the need to invoke the function. The variable has to reside in memory, while the macro definition is just a preprocessor directive. At compile time, all instances of SOMEVALUE will be replaced by the integer value 5. Whereas, a will be a value tucked away in memory somewhere, and (disregarding caching in registers) will be read and/or written to and from memory when it’s referenced in code.

Similarly, a struct will collect together multiple variables residing in memory as its members, whereas an enum will simply collect labels for constant integer values. Instances of a label will be replaced by its corresponding integer value at compile time.

Note also that an enum will assign each label a value unique to that label within that enum unless we instruct the compiler otherwise.

Let’s have a look at typical use of an enum for recording the state of a traffic light of some sort:

enum state {
    STATE_RED,
    STATE_ORANGE,
    STATE_GREEN
}

void
car(enum state light_state) {
    switch (light_state) {
    case STATE_ORANGE:
        slow_down();
    case STATE_RED:
        handbrake();
        break;
    case GREEN:
        go();
        break;
    }
}

No part of this code really hints that the members of the enum are being stored as integers. This level of abstraction makes the code more succinct and improves clarity. In fact, we don’t need to know, nor care, how the members of this struct are being stored. As far as we are concerned, we have just created a new type within our program which C is able to switch on—neat!

Let’s look at a similar piece of code constructed naïvely with a struct rather than an enum:

/* don't do it this way! */

struct {
    int red;
    int orange;
    int green;
} state = { .red = 0,
            .orange = 1,
            .green = 2 };

void
car(int light_state) {
    switch(light_state) {
    case state.orange:
        slow_down();
    case state.red:
        handbrake();
        break;
    case state.green:
        go();
        break;
    }
}

Oof.

That’s a bit of a mouthful in comparison. It is probably tidier than code that a newbie might write, using an anonymous struct and initialisation. Regardless, it is foolhardy to even consider this approach. What happens if we have a dozen or more values to record. That’s going to get messy quickly.

It is unlikely in this example, but what happens as we add and remove members of this frankenstein struct? We must manually renumber them, and humans are prone to making mistakes. Computers are excellent at doing such menial tasks, so why not let them?

Also, the abstraction of creating a new type is gone entirely; we are definitely fiddling with boring old integers.

Final thoughts

I could probably ramble for eight more screen-heights of text about the ins and outs of enums, but I am sure someone else has already done this.

I hope I have illustrated the primary difference between enums and structs in C and helped avoid any confusion.

When looking at programming constructs, if a more experienced programmer asks you “why?” and your answer is nothing better than “why not?”, you should sit tight and heed the advice they will give. They have been through it all before, probably the hard way.