Skip to content
Enums and Pattern Matching
step 1/7

Reading — step 1 of 7

Learn

~3 min readTypes, Enums, and Errors

Swift enums are far more powerful than the C-style enums most languages have. They support associated values, raw values, methods, and rich pattern matching. The Swift Programming Language book treats them as foundational data modeling.

Basic enum

enum Direction {
    case north
    case south
    case east
    case west
}

let d = Direction.north
// or:
let e: Direction = .south       // type inferred, shorthand

switch d {
case .north: print("up")
case .south: print("down")
case .east, .west: print("sideways")
}

The compiler ENFORCES exhaustive switch — miss a case and you get a compile error. Add new cases to the enum and every switch lights up — refactor-safe.

Associated values — Swift's killer feature

Each case can carry typed payload data:

enum Result {
    case success(value: Int)
    case failure(message: String)
}

let r = Result.success(value: 42)

switch r {
case .success(let value):
    print("got \(value)")
case .failure(let message):
    print("error: \(message)")
}

This is how Swift expresses sum types — a value is exactly one of several alternatives, each with its own data. Cleaner than:

  • A class hierarchy (overkill for a closed set)
  • An object with optional fields (every read needs to check)
  • Discriminated unions in TypeScript (similar idea, different syntax)

Real-world example — JSON parsing:

enum JSONValue {
    case null
    case bool(Bool)
    case number(Double)
    case string(String)
    case array([JSONValue])
    case object([String: JSONValue])
}

A single type representing all JSON shapes, with the compiler's help to handle every case.

Raw values

For enums where each case has a fixed underlying value (Int, String, etc.):

enum Status: String {
    case active = "active"
    case archived = "archived"
    case pending = "pending"
}

let s = Status.active
print(s.rawValue)               // "active"

let s2 = Status(rawValue: "archived")     // Status?

Status(rawValue:) is a failable initializer — returns nil for unknown values. Useful for parsing strings into typed enums.

For Int raw values, Swift auto-numbers from 0:

enum Priority: Int {
    case low = 1
    case medium
    case high          // = 3, auto-incremented
}

Methods on enums

enum CompassPoint {
    case north, south, east, west
    
    func opposite() -> CompassPoint {
        switch self {
        case .north: return .south
        case .south: return .north
        case .east:  return .west
        case .west:  return .east
        }
    }
}

CompassPoint.north.opposite()    // .south

Enums can also have computed properties, static methods, and even mutating methods that change self.

Pattern matching

Swift's switch and if case go far beyond simple equality:

// Range matching:
let score = 85
switch score {
case 0..<60: print("F")
case 60..<70: print("D")
case 70..<80: print("C")
case 80..<90: print("B")
case 90...100: print("A")
default: print("invalid")
}

// Tuple matching:
let point = (2, 0)
switch point {
case (0, 0): print("origin")
case (_, 0): print("on x-axis")
case (0, _): print("on y-axis")
case let (x, y): print("at \(x), \(y)")
}

// where clauses:
switch (x, y) {
case let (a, b) where a == b: print("on diagonal")
case let (a, b) where a > 0 && b > 0: print("first quadrant")
default: print("elsewhere")
}

// if case for single-pattern matching:
let maybeNum: Int? = 5
if case let .some(value) = maybeNum {
    print("got \(value)")
}

Recursive enums (indirect)

For tree-shaped data:

indirect enum Tree {
    case leaf(Int)
    case node(left: Tree, right: Tree)
}

let t = Tree.node(
    left: .leaf(1),
    right: .node(left: .leaf(2), right: .leaf(3))
)

indirect lets a case reference the enum itself (otherwise infinite size). Use for trees, lists, expression ASTs.

Common mistakes

  • Using an enum when a struct fits better — if you find yourself with one case carrying many fields, consider a struct.
  • Forgetting exhaustive switch — covering with default: works but loses the compile-time check when you add a new case.
  • Confusing raw values with associated values — raw values are static metadata; associated values vary per instance.
  • Mutating an enum without mutating — methods that change self need the keyword.
  • Comparing enums with associated values via == — only works if you implement Equatable (Swift does it automatically for enums where all associated values are Equatable in Swift 4.1+).

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…