Listing all cases in an enum
Safety over simplicity
Swift language was designed with a focus on “safe by default” approach (do not confuse with “safe by design” — Swift does not prevents you from doing dangerous actions but only encourages for good practices). One of such incentive is a great support for sum and product data types,
struct, respectively. These types are great to limit number of potential states during compilation time so we don’t have to write any unit tests of cases that should never occur — you definitely prefer to never reach given state, rather than covering it with an obvious unit test.
Despite enums are super-powerful feature in our toolbox, you may find out that there is no simple and safe way to list all cases in an
enum (I mean an enum without associated values, of course). I run into a problem of presenting UI picker with all potential states (modelled as an enum) that user could choose from. I needed just an array but Swift does not provide it out-of-the-box and swift-evolution proposal is still waiting for its turn for a review (detailed discussion and its history is available here).
Very often, Swift community suggests two workarounds to solve my problem (inspiration comes from https://theswiftdev.com/2017/01/05/18-swift-gist-generic-allvalues-for-enums/):
- make an
Intand increment a counter until you cannot anymore create a case:
- cast raw memory representation into an enum case
Both of those workarounds are not ideal: first assumes that developer assigns subsequent values (starting from
1 here) for all enums, while second solution may break when Swift changes enum memory representation (we still lack ABI stability in Swift).
In this post I wish to present another technique to list all of enum cases that is safe and independent of Swift implementation details .
To achieve that we can define dedicated
RawRepresentable type for your enum and make this type expressible by
Int literal. Keep in mind that if we try to initialise an enum using
init(rawValue:), Swift takes all values of your
RawRepresentable (that have corresponding case) one by one and compare it with your
rawValue until finding equal value. Therefore, if we try to initialise with some
rawValue that does not match any case, we will have a chance to gather all rawValues with corresponding enum inside
== operator implementation.
Let’s define our goal: find an array with all cases of
BestEnum that is represented by a
Keep in mind that
enum BestEnum: String is actually just a syntactic sugar for conforming to
RawRepresentable protocol with
RawValue = String. Let’s see how could we implement it manually:
We all love language features that generate boilerplate for us (other examples include:
Codableprotocol in Swift 4 and so awaited auto-synthesized
Equatableconformance in Swift 5).
Unfortunately, for auto-conforming to
RawRepresentable we can only use
Int rawValues. If you try to use there your own type (let’s say
enum BestEnum: TypeToBeAutoRepresentable), Swift compiler complains with:
raw type ‘TypeToBeAutoRepresentable’ is not expressible by any literal
Error message is actually a great hint and reveals that my previous statement that “only
Int are possible” was invalid — it is enough that your type has to be expressible by a literal.
Fair enough, let’s define our new type that meets this requirement and call it
BestEnumRaw. It has no special logic — it just stores
String literal as a property value and allows to initialize it with no argument (I will discuss later why do we need dummy
If we now try to use this struct as a raw representation(
enum BestEnum: BestEnumRaw), swift compiler still complains:
‘BestEnum’ declares raw type ‘BestEnumRaw’, but does not conform to RawRepresentable and conformance could not be synthesized
On the other hand, this error message is a bit misleading: our
BestEnumRaw actually conforms to
RawRepresentable, why does Swift suggest that conformance cannot be synthesized? Solutions is really simple: let’s conform to
Equatable protocol and the error goes away:
So far, so good. Our code compiles and definition of our enum is self-explanatory:
Here comes a trick: let’s think what happens if you try to initialise
BestEnum with some
BestEnumRaw, let’s say
BestEnum(rawValue: BestEnumRaw())? Inference type says that a result of this expression is
BestEnum?, so result of this expression should return some
BestEnum case or
nil, when provided
rawValue does not correspond to any case. One may ask: how does Swift check if a given
rawValue corresponds to an existing case? It just iterates over all cases and checks if its representable is equal to the value provided in an initialiser. Don’t be scared, it’s really simple. In our case it checks if:
BestEnumRaw() == BestEnumRaw(stringLiteral:"my_case")?
BestEnumRaw() == BestEnumRaw(stringLiteral:"other_case")?
Now it makes sense why does compiler complain when
BestEnumRawdoes not conform to
Equatableprotocol — otherwise it wouldn’t be able to compare both
RawRepresentables and therefore impossible to create an
Of course both comparisons return
false so our initialiser fails and returns
nil. Nevertheless, we had a chance to catch all possible
BestEnumRaw instance that actually have corresponding enum case. Ha! By having a list of all instance of raw representation, it is so trivial to create a list of all enum cases!
Catching all rawValues
So let’s add static variable
all to our enum as a bucket for all rawValues:
and fill it whenever Swift observers comparison of non-trivial
Do you remember that we have a dummy initializer of
BestEnumRaw? Thanks to that if
rawString == nilwe know that it was not initialized from any literal so does not correspond to any enum.
Implementation is ready: we just need to call dummy initializer
BestEnum(rawValue: BestEnumRaw()) and
String values of all cases 🎉
If you want to achieve an array of actual cases instead of Strings, just map them to a raw object and later flatMap to an enum.
Skip boilerplate code with
Solution described above is straightforward and you can implement it within couple of minutes for your enum (have a look on gist for a final code). However, if you feel it is cumbersome to implement custom
RawRepresentable type for each enum, you can use third-party library EnumList that does it for you.
EnumList provides generic struct that you can use as a
RawRepresentable. Let me show you how we can define our sample
EnumListIntRaw<T>) as raw representable, where
T defines a bucket struct to keep all rawValues. I suggest nested struct
Values into your enum, so that fetching all values expression is self-explanatory:
If you need to parse your enum into/from JSON,
EnumListworks perfectly with
This post describes how to solve particular problem to list all enum cases, which Swift does not provide out-of-the box. Even if you hardly ever need to use it, my aim was to encourage you to find some non-obvious workarounds before compromising Swift safety.
If you think that enum with incrementing
Int rawValue is easier to implement, I want to warn you that it works nicely only for
Int representation and it can be a trap for new developers not aware of continuous rawValues requirement. On the other hand, playing with
withUnsafePointer is quite dangerous since compiler will not warn us if something has changed with in-memory representation— in such a case application would crash during runtime or worse.
Think twice if you ever have to compromise — Compilation-time safety first!
Great news! Swift 4.2 will include out-of-the box solution with auto-conformance for
CaseIterable protocol that provides
allCases colletion. Credits go to Robert Widmann for implementing SE-0194.
More info here.