The problem

Enums, along with classes and structs, play a crucial role in structuring your Swift code, allowing you to define data models and behaviors in a clean, organized, and type-safe manner. However, enums, perhaps Apple’s least favorite child, still haven’t received the attention they deserve. Compared to their siblings, enums lack some great features that structs and classes get for free.

One particular feature that we’ll focus on today is KeyPath, a powerful feature that only available for struct and class. Every field on struct or class gets a free key path denoted by a backslash syntax

struct Employee {
    let name: String
    var age: Int
    var address: Address
}

struct Address {
  var city: String
  var country: String
}

\Employee.name\ // KeyPath<Employee, String>
\Employee.address.city // WritableKeyPath<User, Int>

let employee = Employee(name: "Blob", age: 28, address: Address(city: "Ho Chi Minh", country: "Vietnam"))
employee[keyPath: \.name] // Blob
employee[keyPath: \.address.city] = "Manila"

Enums lack the ergonomic ability to drill down and access data. The Swift API provides only two ways to work with enums: switch and case let, both of which require a lot of boilerplate code just to access the associated data within an enum.

enum SubscriptionType {
    case pro(renewal: Date)
    case free
}

var subsription: SubscriptionType

...

if case let .pro(renewalDated) = subscription else {
    print(renewalDated)
}

// or

switch subscription {
case let .pro(renewalDated):
    print(renewalDated)
default: 
    break
}

The situation becomes even more cumbersome when dealing with nested enums. For example

enum LoadingStatus {
    case idle
    case loading
    case loaded(SubscriptionType)
    case failure(Error)
}

Introducing CasePath

So, is it possible for enums to have the same features as their siblings? Wouldn’t it be nice to do this instead of switch or case let ?

subscription[keyPath: \.pro.renewalDate] 
loadingStatus[keyPath: \.loaded.pro.renewalDate]

While researching this problem, I came across the swift-case-paths package developed by the PointFree team and instantly fell in love with it. I’ve been using it in all my projects ever since. So the PointFree team introduced a new concept called CasePath. Simply put, case paths are key paths, but for enums.

All you have to do is to annotate your enum with @CasePathable macro. The Swift Syntax will generate the necessary boilerplate that allows you to drill down to an enum’s associated type as if it were a key path.

@CasePathable
enum SubscriptionType {
    ...
}

@CasePathable
enum LoadingStatus {
    ...
}

var loadingStatus = LoadingStatus.loaded(.pro(renewal: Date(...)))

loadingStatuss[case: \.loaded.pro] // Date?

You can also do case matching

let subscription = SubscriptionType.free
        
subscription.is(\.free) // true
subscription.is(\.pro) // false

Or mutate the associated value

func renewPro() {
    subscription.modify(\.pro) {
        $0.addTimeInterval(365 * 24 * 60 * 60)
    }
}

Struct dot chaining syntax for enum

Recall how easy it is to access the data inside a struct or class using the dot syntax.

employee.name
employee.address.country

CasePath has you covered—you can use @CasePathable together with @dynamicMemberLookup to enable dot-chaining syntax for enums. How neat! 🙌

struct User {
    let username: String
    let subscriptionType: SubscriptionType
}
    
@CasePathable
@dynamicMemberLookup
enum SubscriptionType {
    ...
}

let blob = User(username: "Blob", subscriptionType: .free)
let klob = User(username: "Klob", subscriptionType: .pro(renewal: Date(...))

blob.subscriptionType.pro // nil
blob.subscriptionType.pro // Date(...)

Moreover, this also enables key path expressions for enum case. Now it can be use in function that accepts key path

let users: [User] = [...]
let renewalDates = users.compactMap(\.subscriptionType.pro) // [Date]
let renewalDates = users.map(\.subscriptionType.pro) // [Date?]

Final words

CasePath unlocks even more power from enums. The PointFree team has been leveraging CasePath in their other open-source projects, such as Composable Architecture and SwiftUI Navigation. It greatly helps in making our code more readable and ergonomic. This article just scratches the surface. Since I’ve been intensively using CasePath in many of my projects, I’ve created a lot of utilities around it. We’ll discuss this more in a future article.