In what order does switch try its cases, and what do where, fallthrough, for-case and ~= do?

27.5.5 · L2 · Interview

Predict first

What does this print?

func describe(_ x: Int, _ y: Int) -> String {
    switch (x, y) {
    case (0, 0): return "origin"
    case (let a, 0) where a > 0: return "pos x-axis \(a)"
    case (_, 0): return "x-axis"
    case let (a, b) where a == b: return "diagonal \(a)"
    case (1...3, _): return "small x"
    case (_, let b) where b.isMultiple(of: 2): return "even y"
    default: return "other"
    }
}
for p in [(0, 0), (5, 0), (-2, 0), (4, 4), (2, 9), (9, 8), (9, 9)] { print(p, describe(p.0, p.1)) }
func fall(_ n: Int) {
    switch n {
    case 5 where n > 9: print("a")
    case 5: print("b"); fallthrough
    case 6: print("c")
    default: print("d")
    }
}
fall(5); fall(6)
let opts: [Int?] = [1, nil, 3]
for case let x? in opts { print("x", x, terminator: " ") }
print()
for case .some(let x) in opts where x > 1 { print("big", x) }
if case 1...3 = 2, case "a"..."c" = "b" { print("ranges") }
struct Pt { var x: Int }
func ~= (pattern: Int, value: Pt) -> Bool { pattern == value.x }
switch Pt(x: 3) { case 3: print("custom ~="); default: print("no") }
  • origin / pos x-axis 5 / x-axis / diagonal 4 / small x / even y / diagonal 9, then b c, c, x 1 x 3, big 3, ranges, custom ~=
  • origin / pos x-axis 5 / x-axis / diagonal 4 / small x / even y / other, then b, c, x 1 x 3, big 3, ranges, no
  • origin / pos x-axis 5 / pos x-axis -2 / diagonal 4 / small x / even y / diagonal 9, then b c, c c, x 1 x 3, big 3, ranges, custom ~=

Mental model. A sorting hat that stops at the first house that accepts you, even if a later house would fit better.

A switch takes the first case that matches, top to bottom, so order matters: (4, 4) is "diagonal" because that case comes before (1...3, _), (9, 9) is diagonal before it can be "even y", and (-2, 0) misses the first axis case (its where fails) and lands in the plain x-axis case. fallthrough jumps into the next case body without evaluating its pattern: fall(5) prints "b" then "c", fall(6) only "c".

for case let x? in opts and for case .some(let x) in opts where x > 1 filter and unwrap in the loop header. Range and tuple patterns use the ~= operator; defining ~= for your own type makes case 3: work on a Pt. The compiler checks enum switches are exhaustive but cannot warn about shadowed overlapping cases on integers, so reading order is your job.

First match wins; fallthrough skips the next pattern check.

Common traps

  • Putting a general case first. It hides the specific ones.
  • Expecting fallthrough to test the next condition. It does not.

They might follow up with

  • Is a where clause part of exhaustiveness? No; a case with where does not count as covering its pattern.

Related questions

  • 1.2.5 What is the difference between if case, guard case, and switch on an enum?
  • 1.2.2 Why can switch on an Optional use .none / .some and also nil?

← What do nested optionals and nil assignments do: Int??, dictionaries…   In what order do willSet and didSet run across a subclass, an… →