Closure tuple does not support destructuring in Xcode 9 Swift 4

I just encountered this error as a result of using enumerated().map():

Closure tuple parameter does not support destructuring

I typed the code:

["foo"].enumerated().map

And then pressed Enter 3 times until Xcode autocompleted the closure boilerplate.

The autocomplete seemingly has a bug that causes the above error. The autocomplete produces double-parenthesis ((offset: Int, element: String)) rather than single-parenthesis (offset: Int, element: String).

I fixed it manually and was able to continue:

// Xcode autocomplete suggests:
let fail = ["foo"].enumerated().map { ((offset: Int, element: String)) -> String in
    return "ERROR: Closure tuple parameter does not support destructuring"
}

// Works if you manually replace the "(( _ ))" with "( _ )"
let pass = ["foo"].enumerated().map { (offset: Int, element: String) -> String in
    return "works"
}

Possibly the result of using Xcode 10.0 beta (10L176w)


It's a side-effect of this proposal for Swift 4:

SE-0110 Distinguish between single-tuple and multiple-argument function types.

But some features included in this proposal caused some regression which is addressed in this post of the evolution-announce mailing list:

[swift-evolution-announce] [Core team] Addressing the SE-0110 usability regression in Swift 4

So, you can expect in the future beta or GM version of Xcode 9, your code would compile well again. Until then, you can use this sort of workaround:

internal func flatMap<KeyPrime , ValuePrime>(_ transform: (Key, Value) throws -> (KeyPrime, ValuePrime)?) rethrows -> [KeyPrime : ValuePrime] {
    return Dictionary<KeyPrime,ValuePrime>(elements: try flatMap({ pair in
        let (key, value) = pair
        return try transform(key, value)
    }))
}

By the way, in Swift 4, Dictionary has some new initializers which take Sequence of (Key, Value) pair. For example:

init(uniqueKeysWithValues: S)


I'm using Xcode 11.1 and Swift 5, and ran into this error while using enumerated().map(). I think this example simplifies things a little, but in general this is what fixed it for me. The true error was the compiler unable to infer to return value:

// Correct Syntax
let resultModels: [ResultModel] = array.enumerated().map { index, model in
  // code
}

// Results in the error Closure tuple does not support destructuring
let resultModels = array.enumerated().map { index, model in
  // code
}

Let's start with the definition of flatMap for a dictionary which is the following:

func flatMap(_ transform: (Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult]

You see that the transform closure takes only one parameter of type Element where Element is just a typealias for a tuple:

public typealias Element = (key: Key, value: Value)

So the first and only argument of the closure should be a tuple of two elements (key of type Key and value of type Value).


Now, if you look at your code (which compiles in Swift 3), you will see that this is not the case, and you should be asking why does this even work in Swift 3.

try flatMap({ (key, value) in
    return try transform(key, value)
})

Your closure takes 2 arguments instead of one (key of type Key and value of type Value). This works in Swift 3 thanks to a feature called destructuring where the compiler will automatically transform a tuple of 2 elements into 2 arguments.

But this feature is weird, rarely used and gives unexpected results most of the time so it has been removed in Swift 4.
Edit: As pointed out by OOPer, this feature has been temporarily removed in Swift 4 beta but should be re-added before the final version is out.

Instead you should be writing:

try flatMap({ tupleArgument in
    return try transform(tupleArgument.key, tupleArgument.value)
})

And your flatMap function becomes:

func flatMap<KeyPrime, ValuePrime>(_ transform: (Key, Value) throws -> (KeyPrime, ValuePrime)?) rethrows -> [KeyPrime:ValuePrime] {
    return Dictionary<KeyPrime, ValuePrime>(elements: try flatMap({ element in
        return try transform(element.key, element.value)
    }))
}