0
0
Swiftprogramming~15 mins

Switch must be exhaustive in Swift - Deep Dive

Choose your learning style9 modes available
Overview - Switch must be exhaustive
What is it?
In Swift, a switch statement must cover every possible value of the variable it checks. This means you cannot leave out any case without handling it explicitly or using a default case. This rule helps prevent unexpected behavior by making sure all possibilities are considered.
Why it matters
This rule exists to make programs safer and more predictable. Without exhaustive switches, some values might be ignored, causing bugs that are hard to find. Imagine a traffic light controller that forgets to handle the green light; it could cause accidents. Swift forces you to think about every option so your code is more reliable.
Where it fits
Before learning this, you should understand basic Swift syntax and how switch statements work. After this, you can learn about pattern matching, enums with associated values, and advanced control flow in Swift.
Mental Model
Core Idea
A Swift switch must handle every possible value to ensure no case is forgotten, making your code safe and complete.
Think of it like...
It's like a checklist for packing your suitcase: you must check off every item you need, or else you might forget something important and be unprepared.
┌───────────────┐
│   switch x    │
├───────────────┤
│ case value1:  │
│   do stuff    │
│ case value2:  │
│   do stuff    │
│ ...           │
│ default:      │
│   do default  │
└───────────────┘

All possible values must be covered by cases or default.
Build-Up - 7 Steps
1
FoundationBasic switch statement syntax
🤔
Concept: Learn how to write a simple switch statement with cases.
In Swift, a switch statement lets you compare a value against multiple possible matches. Each match is called a case. For example: let number = 2 switch number { case 1: print("One") case 2: print("Two") case 3: print("Three") default: print("Other number") } This code checks the number and prints the matching word.
Result
Output: Two
Understanding the basic syntax is essential before learning about exhaustiveness.
2
FoundationWhat does exhaustive mean?
🤔
Concept: Exhaustive means covering all possible cases without missing any.
In Swift, the switch must handle every possible value of the variable's type. For example, if you switch on a Boolean, you must handle both true and false. If you switch on an enum, you must handle all enum cases or provide a default case.
Result
If you miss a case, Swift will give a compile-time error.
Knowing exhaustiveness prevents runtime surprises by catching missing cases early.
3
IntermediateSwitching on enums without default
🤔Before reading on: do you think you can omit the default case when switching on enums? Commit to your answer.
Concept: When switching on enums, you can omit the default if you cover all cases explicitly.
Enums in Swift have a fixed set of cases. If you write a switch that covers every enum case, Swift knows it's exhaustive and you don't need a default. For example: enum Direction { case north, south, east, west } let dir = Direction.north switch dir { case .north: print("Going north") case .south: print("Going south") case .east: print("Going east") case .west: print("Going west") } No default is needed here.
Result
Output: Going north
Understanding this lets you write clearer code that benefits from compiler checks.
4
IntermediateUsing default case for non-exhaustive switches
🤔Before reading on: do you think default always catches all missing cases? Commit to your answer.
Concept: The default case handles any values not matched by previous cases, making the switch exhaustive.
If you cannot or do not want to list all cases, you add a default case. This tells Swift to use default when no other case matches. For example: let number = 5 switch number { case 1: print("One") case 2: print("Two") default: print("Other number") } Here, default covers all numbers except 1 and 2.
Result
Output: Other number
Knowing default is a catch-all helps you avoid compiler errors but can hide missing cases.
5
IntermediateExhaustiveness with value types and ranges
🤔
Concept: Switches on value types like Int require default because values are infinite.
When switching on types like Int or String, you cannot list all possible values. Swift requires a default case to cover the rest. For example: let age = 30 switch age { case 0..<18: print("Minor") case 18..<65: print("Adult") default: print("Senior") } This switch is exhaustive because default covers ages 65 and above.
Result
Output: Adult
Understanding why default is needed for infinite sets prevents confusion and errors.
6
AdvancedExhaustiveness with enums having associated values
🤔Before reading on: do you think you can omit default when switching enums with associated values? Commit to your answer.
Concept: Enums with associated values require careful case handling to be exhaustive.
Enums can have cases that carry extra data. For example: enum Result { case success(data: String) case failure(error: String) } Switching on this enum requires handling both cases, including their associated values: let result = Result.success(data: "File loaded") switch result { case .success(let data): print("Success: \(data)") case .failure(let error): print("Failure: \(error)") } No default needed if all cases are covered.
Result
Output: Success: File loaded
Knowing how to destructure associated values is key to writing exhaustive switches on complex enums.
7
ExpertCompiler enforcement and exhaustiveness surprises
🤔Before reading on: do you think Swift always detects missing cases in complex switches? Commit to your answer.
Concept: Swift's compiler enforces exhaustiveness but can have subtle cases where it misses or requires default unexpectedly.
Swift uses static analysis to check exhaustiveness. However, when using complex patterns like where clauses, tuples, or enums with indirect cases, the compiler might require a default even if all cases seem covered. For example: enum Shape { case circle(radius: Double) case rectangle(width: Double, height: Double) } let shape = Shape.circle(radius: 5) switch shape { case .circle(let r) where r > 0: print("Circle with positive radius") case .rectangle: print("Rectangle") // Missing default causes error because compiler can't guarantee coverage } Adding default fixes this. This shows compiler limitations and the need to sometimes add default for safety.
Result
Compiler error without default; compiles with default.
Understanding compiler behavior helps avoid frustration and write robust exhaustive switches.
Under the Hood
Swift's compiler analyzes the switch statement's cases against the variable's type. For enums, it checks if all cases are explicitly handled. For value types with infinite possibilities, it requires a default case. The compiler uses pattern matching and static analysis to ensure no value can slip through unhandled, preventing runtime errors.
Why designed this way?
This design was chosen to improve code safety and clarity. By forcing exhaustiveness, Swift prevents bugs caused by unhandled cases, which are common in other languages. Alternatives like optional warnings or runtime checks were rejected because compile-time guarantees are more reliable and efficient.
┌─────────────────────────────┐
│        switch statement      │
├─────────────┬───────────────┤
│ Input value │   Cases list   │
├─────────────┼───────────────┤
│ Enum type   │ All enum cases │
│ Value type  │ Explicit cases │
│             │ + default case │
└─────────────┴───────────────┘
          ↓
┌─────────────────────────────┐
│ Compiler checks coverage     │
├─────────────────────────────┤
│ If all cases covered → OK    │
│ Else → Compile-time error    │
└─────────────────────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Can you omit default when switching on Int? Commit to yes or no.
Common Belief:You can omit default if you cover some common Int ranges.
Tap to reveal reality
Reality:You must include a default case because Int has infinite values.
Why it matters:Omitting default causes compile errors and confusion about switch completeness.
Quick: Does covering all enum cases always mean no default needed? Commit to yes or no.
Common Belief:If you list all enum cases, you never need a default.
Tap to reveal reality
Reality:Usually true, but with complex patterns or where clauses, compiler may still require default.
Why it matters:Assuming no default needed can cause unexpected compiler errors.
Quick: Does default case always mean your switch is exhaustive? Commit to yes or no.
Common Belief:Adding default means you handled all cases safely.
Tap to reveal reality
Reality:Default covers unknown cases but can hide missing explicit handling, reducing code clarity.
Why it matters:Overusing default can mask bugs and make code harder to maintain.
Quick: Can you rely on runtime errors if switch is not exhaustive? Commit to yes or no.
Common Belief:If you forget cases, the program will crash at runtime, so it's okay to skip some cases.
Tap to reveal reality
Reality:Swift prevents this at compile time; you cannot compile code with non-exhaustive switches.
Why it matters:Relying on runtime errors is unsafe and Swift's design avoids this.
Expert Zone
1
Switch exhaustiveness interacts with pattern matching complexity; subtle patterns can confuse the compiler.
2
Enums with indirect or recursive cases may require default even if all cases seem covered.
3
Using default can disable compiler warnings about missing cases, so prefer explicit cases when possible.
When NOT to use
Avoid omitting default only when you can guarantee all cases are covered, such as with simple enums. For types with infinite values or complex patterns, always use default. If you need partial matching, consider if-else chains or guard statements instead.
Production Patterns
In production, exhaustive switches are used to handle all enum cases explicitly for clarity and safety. Default cases are used cautiously, often logging unexpected values. Pattern matching with where clauses is combined with exhaustiveness checks to handle complex logic safely.
Connections
Pattern Matching
Builds-on
Understanding exhaustiveness helps grasp how pattern matching ensures all cases are handled safely.
Error Handling
Related concept
Exhaustive switches prevent unhandled cases, similar to how error handling ensures all errors are caught.
Legal Contracts
Analogy in law
Just like a contract must cover all scenarios to avoid disputes, exhaustive switches cover all cases to avoid bugs.
Common Pitfalls
#1Forgetting to handle all enum cases without default.
Wrong approach:enum Color { case red, green, blue } let c = Color.red switch c { case .red: print("Red") case .green: print("Green") // Missing .blue case and no default }
Correct approach:enum Color { case red, green, blue } let c = Color.red switch c { case .red: print("Red") case .green: print("Green") case .blue: print("Blue") }
Root cause:Misunderstanding that all enum cases must be handled explicitly or with default.
#2Omitting default when switching on Int.
Wrong approach:let number = 10 switch number { case 1: print("One") case 2: print("Two") // No default case }
Correct approach:let number = 10 switch number { case 1: print("One") case 2: print("Two") default: print("Other number") }
Root cause:Not realizing Int has infinite possible values requiring default.
#3Using default too early, hiding missing cases.
Wrong approach:enum Direction { case north, south, east, west } let dir = Direction.north switch dir { default: print("Any direction") }
Correct approach:enum Direction { case north, south, east, west } let dir = Direction.north switch dir { case .north: print("North") case .south: print("South") case .east: print("East") case .west: print("West") }
Root cause:Using default as a shortcut prevents compiler from checking missing cases.
Key Takeaways
Swift requires switch statements to be exhaustive, covering all possible values of the variable.
For enums, you can omit the default case if you handle every case explicitly.
For types with infinite values like Int, a default case is mandatory to cover all possibilities.
Using default cases can hide missing cases, so prefer explicit handling when possible.
Understanding exhaustiveness helps write safer, clearer, and more reliable Swift code.