Weekly Swift articles, podcasts and tips by John Sundell.

Enums with custom raw types

Published on 22 Apr 2020
Basics article available: Enums

Swift enums are very commonly used to create exhaustive lists of raw values (such as strings or integers) that can then be worked with in a type-safe manner. For example, here’s how we could define a VideoFormat enum that contains a list of formats based on their file extensions:

enum VideoFormat: String {
    case mp4
    case webM = "webm"
    case ogg
}

When declaring a raw value-based enum like the one above, the compiler will automatically make it conform to the RawRepresentable protocol — which in turn gives us access to APIs like the init?(rawValue:) initializer, as well as the rawValue property — enabling us to easily convert raw values to and from instances of our enum.

While it might initially seem like that sort of functionality requires an enum’s raw type to be a built-in one (such as String), it actually works with completely custom ones too — as long as they both conform to Equatable, and can be expressed using either a string or numeric literal.

As an example, let’s say that we’re working on an app that uses the following Path type to model file system paths in a more strongly typed manner (compared to always passing raw strings around):

struct Path: Equatable {
    var string: String
}

Now wouldn’t it be cool if we would be able to define an enum with Path-based raw values? For example in order to specify an exhaustive list of targets for certain file system operations, like this:

enum Target: Path {
    case content
    case resources
    case images = "resources/images"
}

The cool thing is that all we need to do to make the above possible is to make our Path type conform to ExpressibleByStringLiteral — and the compiler will take care of the rest:

extension Path: ExpressibleByStringLiteral {
    init(stringLiteral: String) {
        string = stringLiteral
    }
}

The fact that enums can have completely custom raw types is yet another example of how so much of Swift’s core functionality is implemented using its own type system — which in turn makes it possible for our custom types to behave exactly like the built-in ones do.