`Array {}` compiles to array of type `[() -> ()]`

So I just found this out. Not sure if it's a bug or somehow expected behavior.

let foo = Array {}

The code above works, and foo is inferred to be of type Array<() -> ()> aka [() -> ()] with a single element inside: an empty closure () -> () (which I assume is our {}).

Digging deeper into which initializer is responsible for the instantiation, turns out it's init(arrayLiteral elements: Element...).

Which is not meant to be used directly. However I don't see a reason why the statement above would resolve to that initializer being used.

Additionally, I attempted to do the same on other types. Every other type errors out as expected, but not for the reason you'd expect. It seems like the errors still reference the type () -> () of our {} closure as a candidate for initialization.

Any thoughts?

1 Like

It’s using trailing closure syntax.

1 Like

init(arrayLiteral:) can be used directly as any other initializer. What you're seeing is the shorthand syntax for trailing closures. If a function parameter has a function type (in this case () -> ()), it can be added at the end of the function call without its argument label.

func foo(label parameter: (Int) -> Int) { ... }

// the following are equivalent
foo(label: { x in x + 1 })
foo { x in x + 1 }

In this case these are equivalent:

// {} is an empty closure of type () -> ()
let foo = Array(arrayLiteral: {})
let foo = Array {}

Regarding Set and Dictionary, the errors are self-explanatory:

  • for Set, the Element type must conform to Hashable and () -> () doesn't conform.
  • for Dictionary, the compiler is trying to find an init which has a function type parameter and in doing so it finds Dictionary.init(grouping:by:). You get multiple errors, one of which is "Missing argument for parameter 'grouping' in call".
4 Likes

It all makes sense now, thank you. As a point of discussion, does it make sense for these initializers to be exposed? Given Apple itself discourages their use, why not just hide them? Similar to the way some SwiftUI properties from protocol requirements are shielded from developer misuse.

To put an example of potential misuse, I was working on a convenience init for Array that takes a single closure, and there was no way for me to disambiguate the Array {} trailing closure behavior from my own init. Of course I could just use parentheses, but I feel the language should favour my init vs a supposedly forbidden one. I know it's a somewhat specific issue, but it feels like a rough edge.

Such use of generic functions is not particular to initializers:

func generic<T>(_ t: T) { 
    // do stuff
}

generic {} // OK, compiles

β€” you can pass an (escaping) closure into virtually any function that has an unbound generic parameter (as Array(arrayLiteral:) does, per Array.Element being unbound). You generally don't have any mechanism to prevent this, as it's, again, not something particular to initializers of arrays or any other collection β€” and that's also what makes Swift's functions first-class types.

Given that, I personally don't understand what's particularly "forbidden" about an array of closures. Could you perhaps provide a more concrete example?

Nothing forbidden about an Array of closures of course. I meant more about the explicit use of the init(arrayLiteral:) initializer when in the docs it explicitly states its direct use is discouraged. I just believe init(arrayLiteral:) shouldn't be available as a public function.

Probably the best way to fix that would be to allow the use of argument labels with the first (or only) closure in trailing closure syntax, but there wasn't consensus for including it in SE-0279.

Great point! That could indeed have done the job of disambiguating it without the noise of extra parenthesis. Sadly it never became a thing.

Terms of Service

Privacy Policy

Cookie Policy