Add @ArrayBuilder to the standard library

Like others, I tend to feel that helpful utilities like this (as opposed to fundamental building blocks) should go into packages rather than the standard library. As other folks have mentioned, putting things like this in the standard library opens up a whole conversation about back-deployment that, if the type can't be back-deployed for some reason, means that many real applications can't start using it until they're willing to shed older OS versions. I wish we would take more advantage of the package ecosystem instead of defaulting to "thing is useful so thing should be in stdlib".

That aside, this would be really useful (in any distribution form) for a couple reasons:

  • We encourage making things only mutable for the shortest duration necessary, and composing arrays that are any more complex than just a static literal makes expressing that uglier. The result builder makes it easy to write more complex dynamic arrays that combine single elements, arrays of elements, and conditions very easily.

  • There have been numerous threads in the past about wanting to support something like this:

    let flags = [
      "abc",
      "def",
      #if os(Linux)
      "ghi",
      #endif
    ]
    

    and using a result builder makes this just work and we can punt on the compiler-conditional lists question for a bit longer.

I would also expect the implementation of the array builder to perform no worse than creating the array the imperative way (in terms of time complexity, number of allocations, etc.). If it couldn't do that, I wouldn't want us to accept it.

One improvement I would make: the usage pattern shown involves computed variables, so they would be re-evaluated every time they're read. I'd rather see an extension that adds an initializer to Array that takes an @ArrayBuilder closure. Then, you can write this:

let someArray: [String] = Array {
  "abc"
  "def"
  if someCondition {
    "ghi"
  }
}

As I was typing that out, I discovered that the following work:

let someArray: [String] = Array { ... }
let someArray: [String] = .init { ... }
let someArray = Array<String> { ... }

But what I really wanted to write, and what I think a lot of people would reach for, would be this:

let someArray = [String] { ... }

which doesn't compile. It looks like the logic that tries to treat a called single-element array literal expression (e.g., [Int](repeating: 0, count: 10)) as an initializer call doesn't work for the case of just a trailing closure, so you have to write this:

let someArray = [String]() { ... }

which is unsatisfying. Might be nice to fix that, if it wouldn't break source somewhere else.

It would also be a shame if builders like this were just limited to arrays. I could see a dictionary one being useful as well, but there's no obvious syntax for a dictionary element in isolation (if we didn't want to abuse an operator for this, we'd have to introduce a special key-value pair type).

To go even further, I think the ultimate end state would be to be able to use this anywhere that an array literal is allowed (so, anything that supports ExpressibleByArrayLiteral), but that would require a whole slew of compiler changes that go beyond what's being pitched here.

4 Likes