The raw value syntax for enums in Swift is “just” a shorthand for conformance to the RawRepresentable
protocol. It’s easy to add this manually if you want to use otherwise unsupported types as raw values.
When you define an enum in Swift, you can choose to have each enum case “backed” by a raw value. For example, here’s an enum that models the terrain type of a map tile in a game, and each case has a raw value of type String
(perhaps because these values are used to encode the information on disk):
enum Terrain: String {
case forest = "F"
case mountain = "M"
case water = "W"
}
You can access an instance’s raw value through the rawValue
property, and there’s a failable initializer to construct an enum value from a raw value (the initializer returns nil
if you pass in an invalid raw value).
Raw values don’t change how enums are stored
Note that adding raw values to an enum doesn’t affect how the enum is laid out in memory. The compiler always determines how many bits it needs to discriminate between all enum cases and then assigns a unique integer tag value to each case. Even for enums with integer raw values, this tag is not the same as the raw value — they are completely different things. This also means you don’t have to worry that an enum with strings as raw values will take up more memory than a “plain” enum — the constant strings are only stored once in the binary, not for each instance.
We can test this by checking the size in memory of a Terrain
value: it’s 1 byte. (Theoretically, an enum with three cases requires only 2 bits of storage, but every value occupies a multiple of 1 byte.) In contrast, a String
containing the corresponding raw value takes up 24 bytes (plus the actual storage for the string’s contents, but that’s statically located in the binary in both cases):1
MemoryLayout.size(ofValue: Terrain.forest) // → 1 (byte)
MemoryLayout.size(ofValue: "F") // → 24 (bytes)
Syntactic sugar for RawRepresentable conformance
In fact, the raw value syntax is simply shorthand for conforming the enum to the RawRepresentable
protocol, which defines the rawValue
property and initializer. When you define the above enum, the compiler generates code equivalent to this:
enum Terrain {
case forest
case mountain
case water
}
extension Terrain: RawRepresentable {
typealias RawValue = String
init?(rawValue: RawValue) {
switch rawValue {
case "F": self = .forest
case "M": self = .mountain
case "W": self = .water
default: return nil
}
}
var rawValue: RawValue {
switch self {
case .forest: return "F"
case .mountain: return "M"
case .water: return "W"
}
}
}
This does exactly the same as the shorthand syntax and should make clear that the raw values don’t affect the enum’s storage.
More options with manual conformance
Once you realize that there’s no magic behind enums with raw values, it opens up the possibility to use all kinds of types as raw values. In the shorthand syntax, raw values can only be String
, Character
, or any integer or floating-point number type. Moreover, the values you provide must be literals, i.e. they must be statically known at compile time.
The RawRepresentable
protocol has no such limitation — its RawValue
associated type can be anything, and the actual raw values can be created dynamically at runtime. Take this example of an enum for modeling colors, backed by their correspoding UIColor
values:
import UIKit
// Can’t use the shorthand syntax for UIColor raw values
enum Color {
case red
case green
case blue
}
// But it’s no problem with manual RawRepresentable conformance
extension Color: RawRepresentable {
typealias RawValue = UIColor
init?(rawValue: RawValue) {
switch rawValue {
case UIColor.red: self = .red
case UIColor.green: self = .green
case UIColor.blue: self = .blue
default: return nil
}
}
var rawValue: RawValue {
switch self {
case .red: return UIColor.red
case .green: return UIColor.green
case .blue: return UIColor.blue
}
}
}
// Access a color's rawValue
Color.blue.rawValue is UIColor
// → true
RawRepresentable is more than just enums
RawRepresentable
isn’t limited to enums. It can also make sense for structs and classes to add conformance. Option sets also make use of the protocol (OptionSet
inherits from RawRepresentable
).
-
In our case, since the string value is statically known at compile time, we could use a
StaticString
, which is only 17 bytes big and not 24. Or we could use aCharacter
(9 bytes) since our string contains only a single character. In any case, it’s still much bigger than the enum. You get the idea. ↩︎