swift · memo
In one line: An enum value is exactly one of its cases (a sum type / tagged union);
raw values are one compile-time constant per case, associated values are per-instance
payloads. switch must be exhaustive, patterns bind and filter, and for
library enums @frozen vs @unknown default decides who may add a case.
Download PDF Print view LaTeX source
How it works
- Raw values
enum S: Int { case ok = 200 }: one literal type, fixed at compile time, givesRawRepresentable+ failableinit?(rawValue:). Implicit:Intcounts up from the previous value (start 0);String= the case name. Raw XOR associated — never both. - Associated values
case user(id: Int, name: String): payload per instance, different types per case. Size = largest payload + tag (spare bits reused); payload-less: 1 byte up to 256 cases, a single case: 0 bytes. indirecton the enum or one case: payload boxed on the heap → recursive types (trees, linked lists, ASTs).- Synthesis: payload-less enums are Equatable + Hashable without
declaring it. With payloads: declare it, all payloads must conform.
Comparableby declaration order (Swift 5.3, SE-0266; not for raw-value enums);Codablewith payloads (5.5, SE-0295);CaseIterable→allCasesonly without payloads. - No stored instance properties (
staticones are fine); methods, computed props,mutating functhat assignsself = .x. A caselessenum API {}is a namespace that cannot be instantiated. - Exhaustive
switch: no implicit fallthrough;fallthroughskips the next pattern check and cannot enter a case that binds variables. Swift 5.9:let x = switch s { …}(SE-0380). - Library evolution (
BUILD_LIBRARY_FOR_DISTRIBUTION): enums are non-frozen by default — clients need@unknown default(SE-0192, Swift 5).@frozen(SE-0260) promises no new case ever: faster layout, no@unknownneeded, adding a case breaks ABI. ObjC:NS_ENUMimports non-frozen,NS_CLOSED_ENUMfrozen. Without library evolution every enum is effectively frozen: a new case breaks clients’ switches at compile time.
Example — a state machine
enum Load { case idle, loading, loaded([Item]), failed(String) }
enum Event { case fetch, retry, success([Item]), failure(String) }
extension Load {
mutating func send(_ e: Event) {
switch (self, e) { // tuple pattern
case (.idle, .fetch), (.failed, .retry): self = .loading
case (.loading, .success(let items)): self = .loaded(items)
case (.loading, .failure(let m)) where !m.isEmpty:
self = .failed(m)
default: break // illegal: ignore
}
}
}
if case .loaded(let items) = state { show(items) }
for case .failed(let m) in history { log(m) } // filter + bind
The 8 pattern kinds (TSPL)
| Pattern | Looks like | Note |
|---|---|---|
| wildcard | _ | matches, ignores |
| identifier | x (in let x = …) | binds anything |
| value-binding | let (x, y), .a(let x) | let distributes inward |
| tuple | (200, _), (let c, true) | element-wise |
| enum case | .failed(let m), .some(x) | works on Optional too |
| optional | let x? | sugar for .some(x) |
| type-casting | is Int, let s as String | check / check+cast |
| expression | 400..<500, "GET" | calls ~= |
~=: default for Equatable is==; ranges usecontains. Overloadstatic func ~=(pattern: P, value: V) -> Boolto match your own (e.g. a predicate, a regex).whereis a guard on the case, not a pattern: runs after binding.case .a(let x), .b(let x):— every alternative must bind the same names with the same types.- Where patterns live:
switch,if/guard/while case,for case … in,catch, and everylet.
Interview traps
default:on an SDK enum hides new cases;@unknown defaultstill warns about the ones you did not list.- Persisted raw values: renaming a case (String raw = name!) →
init?(rawValue:)givesnil. Pin raw values. - Equatable on a payload enum is not free: one non-Equatable payload and synthesis fails.
- Adding a case to a public enum is a breaking change either way — compile-time (source libs) or runtime-path (
@unknown). state == .loadingneedsEquatable(payloads too);if case .loading = stateneeds nothing.
Likely questions
- Raw and associated at once? — no; mutually exclusive.
- Why
indirect? — a recursive payload needs a box. @unknown defaultvsdefault? — future cases + keeps the warning.- Why enums over flags/optionals for state? — illegal states unrepresentable.
- How does
case 1...5match? — expression pattern via~=. - Payload without a switch? —
if case let, or a computedvar data: Data?.