- Proposal: TBD
- Author: Erica Sadun
- Status: TBD
- Review manager: TBD
This proposal re-architects case syntax grammar to reduce potential errors and simplify unwrapping enumerations.
Swift-evolution thread:
[Pitch] Reimagining guard case
/if case
In its current design, Swift case binding suffers from two weaknesses.
- Mixed external and internal
let
/var
binding may introduce errors from uncommon edge cases. - Real-world users may not consider the parallel construction between
if case
/guard case
withswitch
statements or naturally connect the two layouts.
When pattern matching, it's common to bind a variable or constant. It's uncommon but legal to use a bound value as an argument. Adopting an "always explicit, always within the parentheses" rule adds consistency and safety to Swift.
Consider the following enumeration and values:
// An enum with one, two, or three associated values
enum Value<T> { case one(T), two(T, T), three(T, T, T) }
// An example with two associated values
let example2: Value<Character> = .two("a", "b")
// A bound symbol
let oldValue = "x"
This code's goal is to conditionally bind newValue
and pattern match the value stored in the oldValue
symbol. The first example succeeds. The second example compiles and runs but does not match the coder's intent. Using an external let
creates a new oldValue
shadow instead of pattern matching oldValue
's stored value.
// Safe
if case .two(let newValue, oldValue) = example2 {
...
}
// Syntactically legal but incorrect
if case let .two(newValue, oldValue) = example2 {
...
}
In-parenthesis binding avoids accidental shadowing. It eliminates this class of error by adding let
and var
key words to each use point. This creates longer call sites but enumerations rarely contain more than three or four associated items.
Adopting point-of-use binding enhances clarity and readability. Both if case let
and if case var
(plus case var
and case let
) may look like single compound keywords rather than a combination of two distinct actions to developers unfamiliar with this syntax.
Swift's guard case
and if case
align statement design with the switch
statement, moving the matched value to the right of an equal sign.
switch value {
case .enumeration(let embedded): ...
}
if case .enumeration(let embedded) = value
The status quo for the =
operator is iteratively built up in this fashion:
=
performs assignmentlet x =
performs bindingif let x =
performs conditional binding on optionalsif case .foo(let x) =
performs conditional binding on enumerations and applies pattern matching
Using if case
/guard case
in the absense of conditional binding duplicates basic pattern matching with less obvious meaning. These two statements are functionally identical:
if range ~= myValue { ... } // simpler
if case range = myValue { ... } // confusing
Issues with the current design include:
guard case
andif case
look like standard non-conditional assignment statements but they are not assignment statements. Using the assignment operator violates the principle of least astonishment.- In
switch
, acase
is followed by a colon, not an assignment operator. - Swift has a pattern matching operator (
~=
) but does not use it here. case
syntax is wordy. The statement includescase
,=
, and optionallylet
/var
conditional binding. Design alternatives could streamline this syntax, enhance clarity, and introduce a more concise format.
This proposal adopts point-of-use conditional binding and recommends one of the following designs. A successful design will replace the current syntax with a simpler grammar that prioritizes pattern matching and support conditional binding.
This design drops the case
keyword and replaces =
with ~=
. The results look like this, showcasing a variety of let
placement, variable binding, and optional sugar alternatives.
guard .success(let value) ~= result else { ... }
guard .success(var value) ~= result else { ... }
if .success(let value) ~= result { ... }
if .success(var value) ~= result { ... }
guard let x? ~= anOptional else { ... }
if let x? ~= anOptional { ... }
In this design:
- The
case
keyword is subsumed into the (existing) pattern matching operator - The statements adopt the existing
if-let
/if var
andguard-let
/guard var
syntax, includingOptional
syntactic sugar.
if let x = anOptional { ... } // current
if case let x? = anOptional { ... } // would be removed
if let x? ~= anOptional { ... } // proposed replacement for `if case`
Pattern matching without conditional binding simplifies to a standalone Boolean condition clause. On adopting this syntax, the two identical range tests naturally unify to this single version:
if range ~= myValue { ... } // before
if case range = myValue { ... } // before
if range ~= myValue { ... } // after
This design introduces new :=
"declare and assign" operator. This operator eliminates the need for explicit let
, although the keyword is allowed and most house style guides would recommend its use:
guard .success(value) := result else { ... }
guard .success(let value) := result else { ... }
if .success(value) := result { ... }
if .success(let value) := result { ... }
guard value? := anOptional else { ... } // newly legal, although unnecessary
guard let value? := anOptional else { ... } // newly legal, although unnecessary
Assignments to variables require the var
keyword, enabling coders to clarify the distinct roles in mix-and-match pattern matching:
guard .pair(value1, var value2) := result else { ... } // implied let
guard .pair(let value1, var value2) := result else { ... } // explicit let
if .success(var value) := result { ... } // variable assignment
guard var x? := anOptional else { ... } // variable assignment
guard var x := anOptional else { ... } // simpler variable assignment
guard var x = anOptional else { ... } // even simpler (current) variable assignment
guard x := anOptional else { ... } // new constant assignment
Adopting this syntax provides more natural results for binding associated enumeration variables.
This proposal does not address switch case
or for case
beyond internal binding requirements.
This proposal is breaking and would require migration. External let
or var
would automatically be moved by fixits into use points. Current guard case
and if case
syntax would be migrated to the new design.
Although removing if case
and guard case
are breaking, this proposal should wait until Swift 4 Stage two to allow proper debate and consideration from the core team.
- Leaving the grammar as-is, albeit confusing
- Retaining
case
and replacing the equal sign with~=
(pattern matching) or:
(to match the switch statement). - Adding
matches
oris
as an alternative to the pattern matching operator