Weird let pattern is valid in Swift with unexpected results

Check this out:

let (x: Int, y: String) = (x: 200, y: "Hello world")

print(Int) // prints "200"
print(String) // prints "Hello world"

I know that it should be written let (x, y): (Int, String) = (200, "Hello word"), but why Swift is accepting the pattern (x: Int, y: String) without compilation errors? After the first line, x and y don't exist as variables, where did they go?

2 Likes

x and y aren't variables in this case; they're tuple element labels. So your first line declares two variables: one named Int which will receive the element labeled x on the right-hand side, and one named String which will receive the element labeled y on the right-hand side.

Names can be shadowed in more-local scopes in Swift, so declaring Int and String as variables like this is technically valid, if confusing.

It's easier to see what's going on when you note that you can drop the labels on both sides (or just one side), which leaves you with this:

let (Int, String) = (200, "Hello world")

or if you use different variable names while keeping the labels:

let (x: someNumber, y: someString) = (x: 200, y: "Hello world")

The trick here is that foo: bar in this context represents tuple element label: value or binding, not binding: type as in non-tuple declarations.

3 Likes

I don't think I knew about this. What is the intended purpose of it?

It seems like maybe, for this? But no.
Type of expression is ambiguous without more context.

struct S {
  let x = 1
  let y = 1
}

let (x: x, y: y) = S()

You can only destructure like that for tuples, not structs. You can do something similar for enums with the case let syntax, though.

1 Like

I don't think it's destructuring. Trying something that fits that description is disallowed:

// Compiles without warning:
typealias S = (x: Int, y: Int)
if case let (x: x, y: y) = S(x: 1, y: 2) { }
typealias S = (y: Int, x: Int)
// Expression shuffles the elements of this tuple; this behavior is deprecated
if case let (x: x, y: y) = S(x: 1, y: 2) { }
typealias S = (y: Int, x: Int, z: Int)
// …tuples have a different number of elements
if case let (x: x, y: y) = S(x: 1, y: 2, z: 3) { }
1 Like

Could you elaborate on what you mean by this? If not destructuring, what is it doing?

I believe you'd have to explicitly discard the unused elements:

let (x: x, y: y, z: _) = (x: 1, y: 2, z: 3)
1 Like