Lets say we have an enum
with associated values:
Enum A {
case a1(x: Int)
case a2(y: String)
}
And some function that switches on this enum
:
func doSomething(a: A) {
switch a {
case .a1(let x):
print("a1 let \(x)")
case let .a2(y):
print("let a2 \(y)")
}
}
For example:
var x = A.a1(x: 1)
var y = A.a2(y: "hello")
doSomething(a: x) // Prints a1 let 1
doSomething(a: y) // Prints let a2 hello
As you notice I can use the syntax case .a1(let x)
and case let .a2(y)
with no apparent difference.
So I wanted to understand: are there valid reasons to have both syntaxes, or they are purely for convenience? For example: are there a lower-level (compiler, memory) difference between those options? Are there any examples when one or the other wouldn't work as well? Thanks
CodePudding user response:
As you notice I can use the syntax case .a1(let x) and case let .a2(y) with no apparent difference.
You're right, there's very little difference between these two syntaxes — the case let
syntax is for convenience.
are there valid reasons to have both syntaxes, or they are purely for convenience? For example: are there a lower-level (compiler, memory) difference between those options?
Yes, but it best applies to enum
cases with more than one associated value, as it allows you to avoid having to repeat let
for every single associated value in the case:
enum A {
case a1(x: Int)
case a2(y: String)
case a3(x: Int, y: String)
case a4(x: Int, y: String, z: Double)
}
let a: A = ...
switch a {
// Inner `let`:
case .a1(let x):
print(x)
// Equivalent outer `let`:
case let .a2(y):
print(y)
// Inner `let`s can get repetitive:
case .a3(let x, let y):
print(x, y)
// More convenient outer `let`s:
case let .a4(x, y, z):
print(x, y, z)
}
An explanation from the Swift language guide:
If all of the associated values for an enumeration case are extracted as constants, or if all are extracted as variables, you can place a single var or let annotation before the case name, for brevity
This is a purely syntactic convenience, and makes absolutely no difference in compilation.
If you're curious about how this shakes out from the language grammar:
switch
statements have a list of switch cases- Regular cases (non-
default
cases) have a case label (which has one or more case items) - Case items are bound as patterns
- Patterns are where the syntax branches:
- In the
case let .a4(...)
variant, thepattern
is bound as a value binding pattern whose internal pattern binds as an enum case pattern - In the
case .a3(let...)
variant, thepattern
is bound directly as an enum case pattern, whose tuple pattern contents each bind a value binding pattern
- In the
The grammar technically allows for a lot of flexibility here, but the compiler ensures that you either have a var
/let
outside of the case label itself, or bind each variable inside of the case with a var
/let
internally
CodePudding user response:
This is only a syntactic difference. Consequently, x
is bound to the enum case's associated value whether you write let .someCase(x)
or .someCase(let x)
as the pattern of the switch case.
.someCase(let x)
This is an enum case pattern, with a value-binding pattern (let x
) within it. Within the value-binding pattern, there is an identifier pattern x
. As an abstract syntax tree, it looks like this:
enum case pattern
|
value-binding pattern
|
identifier pattern
let .someCase(x)
In this case, the value-binding pattern is the root of the tree instead:
value-binding pattern
|
enum case pattern
|
identifier pattern
Note that the syntax for value-binding patterns is very flexible, any kind of pattern can follow the keyword let
/var
.
value-binding-pattern ::= 'let' pattern | 'var' pattern
According to the value-binding patterns section of the Swift guide,
Identifiers patterns within a value-binding pattern bind new named variables or constants to their matching values.
In both cases, the identifier pattern is nested within the value-binding pattern, directly or indirectly. Therefore, x
is bound to the matched associated value in the same way.
It might seem like writing let
first thing in the pattern is always a good idea since it binds all the identifiers to the newly matched values in one go. But sometimes you don't actually want to bind everything:
enum Foo {
case someCase(Int, Int, Int)
}
func someFunction(someParameter: Int, foo: Foo) {
switch foo {
// if you wrote "let .someCase(someParameter, y, z)" here,
// then someParameter would be bound to the associated value, rather than
// the function's parameter!
case .someCase(someParameter, let y, let z):
break
default:
break
}
}