- Proposal: TBD
- Author: Erica Sadun
- Status: TBD
- Review manager: TBD
This proposal re-architects guard case and if case grammar for unwrapping complex enumerations. It drops the case keyword from if and guard, replaces = with ~=, and introduces the := operator that combines declaration with assignment.
Swift-evolution thread:
[Pitch] Reimagining guard case/if case
Swift's guard case and if case design aligns statement layout with the switch statement:
switch value {
case let .enumeration(embedded): ...
}
if case let .enumeration(embedded) = valueThis grammar unifies the two approaches and offers an overall conceptual "win". However, real-world users do not think about this parallel construction or naturally connect the two layouts.
guard caseandif caselook like assignment statements but they are not assignment statements. This violates the principle of least astonishment.- In
switch, acaseis followed by a colon, not an assignment operator. - Swift has a pattern matching operator (
~=) that isn't used here. casesyntax is wordy, includingcase,=, and optionallylet/varassignment.
guard case and if case perform simultaneous pattern matching and conditional binding. These examples demonstrate their use for a simple one-associated-value enumeration:
enum Result<T> { case success(T), error(Error) }
// valid Swift
guard case let .success(value) = result
else { ... }
guard case .success(let value) = result
else { ... }
// valid Swift
if case .success(let value) = result { ... }
if case let .success(value) = result { ... }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) =andif case let .foo(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 { ... } // confusingThis proposal replaces the current syntax with a simpler grammar that prioritizes pattern matching but mirrors basic conditional binding. The new syntax drops the case keyword and replaces = with ~=. The results look like this:
guard let .success(value) ~= result else { ... }
guard .success(let value) ~= result else { ... }
if let .success(value) ~= result { ... }
if .success(let value) ~= result { ... }
guard let x? ~= anOptional else { ... }
if let x? ~= anOptional { ... }The design includes Swift's current let-placement flexibility and let-var mix-and-match placement. Users may choose to use var instead of let to bind to a variable instead of a constant. In this design:
- The
casekeyword is subsumed into the (existing) pattern matching operator - The statements adopt the existing
if-let/if varandguard-let/guard varsyntax, includingOptionalsyntactic sugar.
if let x = anOptional { ... } // current
if case let x? = anOptional { ... } // current, would be removed
if let x? ~= anOptional { ... } // proposed replacement for `if case`Introducing a further new := "declare and assign" operator eliminates the need for explicit let:
guard .success(value) := result else { ... } // clean and elegant
if .success(value) := result { ... } // clean and elegant
guard x? := anOptional else { ... } // newly legal, although unnecessaryAssignments to variables require the var keyword, and let will be permitted even if it is not required, 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 assignmentPattern 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 { ... } // afterThis proposal does not address switch case or for case.
This proposal is breaking and would require migration.
- Leaving the grammar as-is, albeit confusing
- Retaining
caseand replacing the equal sign with~=(pattern matching) or:(to match the switch statement).