SE-0508: Array expression trailing closures

Hi everyone,

The review of SE-0508: Array expression trailing closures begins now and runs through February 12, 2026.

Reviews are an important part of the Swift evolution process. All review feedback should be either on this forum thread or, if you would like to keep your feedback private, directly to the review manager via the forum messaging feature. When contacting the review manager directly, please keep the proposal link at the top of the message.

Try it out

To try out this feature, you can download a toolchain for Linux or macOS.

What goes into a review?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift. When writing your review, here are some questions you might want to answer in your review:

  • What is your evaluation of the proposal?
  • Is the problem being addressed significant enough to warrant a change to Swift?
  • Does this proposal fit well with the feel and direction of Swift?
  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

More information about the Swift evolution process is available here.

Thank you,

Xiaodi Wu
Review Manager

12 Likes

I was going to say this doesn’t seem that necessary, especially if the workaround is to just add parentheses, but if it works for InlineArray then it should be made consistent.

1 Like

Would this new feature enable someone (or the standard library) to add an Array.init that behaves like the motivating InlineArray.init? The examples all seem to be of trailing closures that don’t take any arguments.

This fixes a hole in the language with no adverse side effects that I'm aware of. Obvious +1.

It used to be the case that you couldn't write an extension with type sugar either:

// used to have to write this
extension Array where Element == Int { ... }

// used to be an error, now it isn't
extension [Int] { ... }

So this is just another one of those nice changes that smooth out weird rough edges in the language that shouldn't exist. If we care about having type sugar as a first-class way of writing arrays and dictionaries, then we should care about making sure you can substitute one for the other unless there's some compelling reason that should be disallowed. In this case, no such compelling reason exists—I imagine that it was just an oversight in the type checker when it tries to detect if what was parsed as a literal was actually type sugar in expression position.

As far as I'm concerned, this is really a bug fix, but it's going through Evolution because of the possibility of ever-so-minor source compatibility questions. Those seem unlikely to impact real code (let's check with the source compatibility suite to be sure).

8 Likes

+1 from me. The extension on a fully specified type always gets me when I realize we still can’t do it.

It will support trailing closures with arguments. An Array analog of that InlineArray.init (which passes the current index to the closure) could conceivably be something like:

extension Array {
    init(generate: (Int) -> Element?) {
        self = []
        var index = 0
        while let element = generate(index) {
            append(element)
            index += 1
        }
    }
}

let powersOfTwo = [Int] { index in
    guard index < 10 else { return nil }
    return 1 << (index + 1)
}

To me, the @ArrayBuilder example is a stronger motivating use case, but it’s certainly reasonable and self-consistent for the language to support all of these use cases.

2 Likes

Makes sense. If this works:

extension Array {
    init(closure: () -> Element?) { fatalError("TODO") }
}

typealias T = [Int]

_ = T { 1 } // âś…

And this works:

_ = ([Int]) { 1 } // âś…

Why doesn't this:

_ = [Int] { 1 } // ❌ Error

+1

6 Likes

What happens here for types that already adopt ExpressibleByArrayLiteral?

let value = Set<String> {
  "a"
}
1 Like

Seems like an obvious fix, and it looks like due diligence on compatibility has been done. +1.

1 Like

Is there a specific reason why this can't be generalized to RangeReplaceableCollection?

For initializers, this only affects sugared array and dictionary types using square brackets ([String], [String: String]).

For literals, the only possible interaction is if you can come up with a way to get a bare array literal expression followed by a callAsFunction call (with a trailing closure) to be inferred as a Set literal. I couldn’t come up an example where that works today:

extension Set {
  func callAsFunction(calledAsFunction: Bool) -> Set {
    print(calledAsFunction)
    return self
  }

  func callAsFunction(_ closure: () -> Void) -> Set {
    closure()
    return self
  }
}

// error: cannot call value of non-function type '[Int]
let value: Set<Int> = [1, 2, 3](calledAsFunction: true)

// error: cannot call value of non-function type '[Int]
let value2: Set<Int> = [1, 2, 3]({ print("test") })

// Following this proposal, this would still be an error:
// error: cannot call value of non-function type '[Int]
let value2: Set<Int> = [1, 2, 3] { print("test") }

What would you like to enable? Only Array and Dictionary have the sugared type syntax ([String], [String: String]) that is addressed in this proposal.

2 Likes

I think there's a bit of a misunderstanding of the goals of this specific proposal, possibly because it originated from the earlier @ArrayBuilder pitch (and uses that in some of its examples). This proposal is just a language change targeting a hole in the way type sugar/collection literals are parsed. It is only handling the following cases and making them compile with the expected behavior:

[T] { ... }  // Array<T>.init { ... }
[a, b, c] { ... }  // [a, b, c].callAsFunction { ... }
[K: V] { ... }  // Dictionary<K, V>.init { ... }
[1: "a", 2: "b"] { ... }  // [1: "a", 2: "b"].callAsFunction { ... }

Other types or protocols like Set and RangeReplaceableCollection aren't relevant here.

7 Likes

Oh, my bad! Thanks for clarifying! I do hope we get a generalized collection builder in the standard library, though. :slight_smile:

2 Likes

As we're getting into the second half of the review period, would be curious if any folks have additional feedback, and particularly on the (possibly only theoretical) source break that can occur in the context of result builders.

Honestly, result builders taking a list of closures seem so hard to use that it almost feels like a bug that they were supported at all.

2 Likes