F# — Enums vs Discriminated Unions

In my pre­vi­ous post on dis­crim­i­nat­ed unions, I pre­sent­ed dis­crim­i­nat­ed unions as an alter­na­tive to stan­dard .Net class­es to rep­re­sent hier­ar­chi­cal data struc­tures. How­ev­er, in terms of data struc­ture, dis­crim­i­nat­ed unions share much more sim­i­lar­i­ties with enums than they do class­es – both allow you to define a set of named con­stants and asso­ciate some data with these con­stants.

The syn­tax­es for cre­at­ing enums and dis­crim­i­nat­ed unions in F# are very sim­i­lar too:

image

Despite their appar­ent sim­i­lar­i­ties, there are some sig­nif­i­cant dif­fer­ences between the two:

    • Enums don’t offer a safe­ty guar­an­tee
    • Enums only hold one piece of data
    • Dis­crim­i­nat­ed unions are ref­er­ence types
    • Enums can be used as bit flags

Now let’s take a clos­er look at these dif­fer­ences.

Enums don’t offer a safety guarantee

As enums are lit­tle more than syn­tac­tic sug­ar over a prim­i­tive inte­gral type such as int, there is no guar­an­tee that the val­ue of an enum is valid. For instance, it’s pos­si­ble to cre­ate an instance of an enum type with an inte­gral val­ue that is not asso­ci­at­ed with one of the named con­stants:

image

it’s easy to see how bugs can creep in when you mis­tak­en­ly cre­ate enum val­ues that don’t make any sense, espe­cial­ly when you’re work­ing with enum val­ues from exter­nal sources. Which is why it’s a good prac­tice to check the enum val­ues with the sta­t­ic Enum.IsDefined method.

Dis­crim­i­nat­ed unions, on the oth­er hand, can only be one of the defined val­ues, any attempts to do oth­er­wise will be met with a swift com­pil­er error!

Enums only hold one piece of data

This one is self evi­dent from the ear­li­er snip­pet, enums only hold one piece of data but dis­crim­i­nat­ed unions hold a tuple of data.

Discriminated unions are reference types

Enums are val­ue types and instances of an enum type there­fore reside on the stack as a few bytes. Dis­crim­i­nat­ed unions, as do all oth­er ref­er­ence types, reside in the heap (plus a point­er on the stack whilst it’s still ref­er­enced) and need to be garbage col­lect­ed when they are no longer ref­er­enced.

The impli­ca­tion of this is such that enums offer sig­nif­i­cant per­for­mance ben­e­fits over dis­crim­i­nat­ed unions. Take the fol­low­ing snip­pet for instance, where I pop­u­late two arrays with 10 mil­lion items, one with enums and the oth­er dis­crim­i­nat­ed unions.

image

Aver­aged over three runs, the enum array took 0.048 sec­onds to fin­ish whilst the dis­crim­i­nat­ed union array took 1.919 sec­onds!

Enums can be used as bit flags

From MSDN:

You can use an enu­mer­a­tion type to define bit flags, which enables an instance of the enu­mer­a­tion type to store any com­bi­na­tion of the val­ues that are defined in the enu­mer­a­tor list.

image