Implicit and Optional Switch Statements
Introduction
I suggest adding implicit and optional switch statements, to fix the annoyances many have with the exhaustion detection for the current switch statements, which requires the use of the default
statements in many unnecessary situations. This is because exhaustion is only detected with enumerations.
Motivation
The switch
statement is a powerful way to control flow, despite this, it acts unpredictably, specifically with how and when it requires an explicit default
statement during an attempt to become exhaustive. See the following example where we are using a switch statement to find out the sign of foo
:
let foo: Int = 2
switch foo {
case 0: print("Zero")
case ...0: print("Negative")
case 0...: print("Positive")
}
In this example, we get the following:
Error: Switch must be exhaustive
This switch statement is, in fact, exhaustive, but the compiler cannot detect this. There are many solutions to this, such as:
let foo: Int = 2
switch foo {
case 0: print("Zero")
case ...0: print("Negative")
default: print("Positive")
}
But given more complex examples it can be ambiguous which case the default
handles, and comments explaining can create clutter within the switch statement.
I have even seen some strange cases like:
let foo: Int = 2
switch foo {
case 0: print("Zero")
case ...0: print("Negative")
case 0...: fallthrough
default: print("Positive")
}
While this solves the previous problems of ambiguity, it is non-standard and syntactically messy.
There are many similar examples of a switch statement not being identifiably exhaustive by the compiler.
switch Int.random(1...3) {
case 1: ...
case 2: ...
case 3: ...
}
Error: Switch must be exhaustive
One solution to this would be to make it such that the compiler understands the switch statement's exhaustively for types other than enumerations. There are many more exhaustion detection issues, so finding a solution to all these various cases seems pedantic and pointless hence, why I am pitching this change.
Furthermore, there are many times when a default
case is not necessary.
enum Foo { case A, B, C, D, E, F, G }
let foo: Foo = .A
switch foo {
case .A: ...
case .B: ...
case .C: ...
case .D: ...
default: break //Foo.E, Foo.F, Foo.G are not needed
}
Here is another example while extreme shows how this default
is unnecessary:
let foo = true
switch foo {
case true: ...
case false: ...
default: fatalError()
}
switch
statements are used for more than just pattern matching and checking against all value types of enumerations, they are also used for cleaner and easier to read code.
For example, if we have chaining else-if
statements, that requires no final else
statement, it would be cleaner to write it as a switch statement, but then a default: break
is necessary.
if foo == condition1 {}
else if foo == condition2 {}
else if foo == condition3 {}
...
else if foo == condition9 {}
// This is better written as
switch foo {
case condition1: ...
case condition2: ...
...
case condition9: ...
default: break
}
There should a better, cleaner method to explicitly stating that a default
should be optional or implicit rather than adding a adding a default: break
or a default: fatalError()
Proposed Solution
Optional Switch Statements: switch?
Allows for skipping the required default
statement
switch? foo {
case ...: break
case ...: break
}
This is functionally equal to:
switch foo {
case ...: break
case ...: break
default: break
}
Implicit Switch Statements: switch!
Allows for skipping the required default
statement, but will fail if no case statement is run
switch! foo {
case ...: break
case ...: break
}
This is functionally equal to:
switch foo {
case ...: break
case ...: break
default: fatalError()
}
Detailed Design
This addition would create three different types of switch statements:
switch
: The current switch statement, no functionality change.switch?
: An optional switch statement, which would break out of the switch statement if no case is run.switch!
: An implicit switch statement, which would callfatalError()
if no case is run.
This design choice was made to match the various forms of optional/conditional and implicit/forced castings and keywords:
as, as?, as!
try, try?, try!
foo.method()?, foo.method()!
!
: is used to force something, a runtime crash occurs if this fails
?
: is used to attempt something and skip over it if it fails
Impact on Existing Code
This change is only additive and will not affect any current code.