Why Swift Enums with Associated Values Cannot Have a Raw Value
An enumeration — short: an enum — is a collection of distinct values that somehow belong together, for example a list of airports:
Traditionally, each enum case was only a label for an Integer value. These labels were only necessary to make the code readable for humans while the computer itself was internally working with those Integers.
Swift enums still have that core functionality but you can do a lot more with them.
Raw Values
First of all, you’re not bound to use Integers for the value that is represented by a particular case. You can use Strings, Characters or even Floats instead. If you want to use the three-letter IATA code as the backing value of the enum cases you can do that:
Whatever the type you choose, the value you assign to a case is called a rawValue
.
Associated Values
Now you have a list of airports that are consistently labelled with the name of the city where they are located. But then you realize that there are some cities that have multiple airports: London, for example. In earlier times you would have had to rename all enum cases in order to achieve a consistent naming, e.g.
But we probably agree that this is ugly and less readable than the simple city-labelled enum we had before. The reason is that we are actually mixing up two different types of information: The city where the airport is located and the airport itself. Mathematically speaking, these are two dimensions that we are trying to hammer into a single enum that only has one dimension. (After all, it’s just a list.)
Fortunately, Swift offers a way out of this dilemma: It allows you to bind one (or several) additional values to an enum case. These values are called associated values. We could, for example, use a String to identify the particular airport and as it’s only needed for the city of London in our current list, we only add this associated value to the london case:
Now we’re back to our clean city-labelled enum and only use an additional value to identify the particular airport where needed.
As a String isn’t very type-safe and prone to errors, we can go one step further and replace it with another enum:
Now our Airport enum looks like this:
When we define a variable of this enum type, we can now assign a “regular” enum case to this variable, as well as an enum case with an associated value.
ℹ️ Note: It’s also possible to omit the label for an associated value. For example, we could have defined the london case in the enum as case london(LondonAirportName)
and then used it like this:
The Problem with Associated Values
Now you might have realized that we silently omitted the raw values and the type annotation for the enum as soon as we introduced an associated value for an enum case. We had to do this because Swift doesn’t allow us to have both: raw values and associated values within the same enum.
A Swift enum can either have raw values or associated values.
Why is that?
It’s because of the definition of a raw value: A raw value is something that uniquely identifies a value of a particular type. “Uniquely” means that you don’t lose any information by using the raw value instead of the original value. It’s formalized in Swift with the RawRepresentable protocol whose documentation states:
With a
RawRepresentable
type, you can switch back and forth between a custom type and an associatedRawValue
type without losing the value of the originalRawRepresentable
type.
Mathematically speaking, the mapping from a type to its raw value must be injective. It must always be possible to reconstruct the original value only from a given raw value.
Unfortunately, as soon as we add associated values to our enum cases, we make it impossible for the enum to be injective with respect to its raw value.
Puh! It’s getting way too theoretical, isn’t it? Let’s have a look at an example:
Enums Without Associated Values
Our original enum didn’t have any associated values, only raw values:
So when we set a variable to one of these cases and obtain its raw value:
we can easily reconstruct the original airport with that raw value:
This would work with any of the enum cases, no restrictions.
Enums With Associated Values
Now let’s try to do the same thing with our enum with the associated value and pretend that it had a raw value as well. Instead of the IATA code we simply use a three-letter “city identifier” (which is the same as the IATA code for the first three cases, but different for the London case):
We can still set a variable to one of these cases and even obtain its raw value:
However, we cannot reconstruct the original airport from that raw value because there’s no way to tell which associated value to choose:
That information is lost. And that’s precisely why it’s either — or.
How to Get Your Raw Values Back
When you’re thinking about getting raw values to work with enums with associated values, what you really want is usually something else: A value that uniquely identifies an enum value’s case label, without bothering about its associated value. It’s similar to a raw value, but it’s not the same.
Implementing this value is really simple: You just add a computed property to your enum and return a different value for each case.
You may, of course, also move this variable declaration into an extension on Airport to separate it from the case definitions.
Now if you define a variable like this:
you can always retrieve its case identifier by calling airport.cityIdentifier
instead of airport.rawValue
. (Plus: You even get improved readability as cityIdentifier is definitely more expressive than rawValue.)
Thanks for Reading! ✌️
If you have any questions or corrections, please leave a comment below. This article is also available on my blog. I will announce new articles to the blog via my Twitter account @DerHildebrand.