ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [SE-0508] Array expression trailing closures
    Swift 2026. 2. 14. 15:44

    안녕하세요. 그린입니다 🍏

    이번 포스팅에서는 SE-0508 Array expression trailing closures에 대해 정리해보겠습니다 🙋🏻


    Intro

    Swift에서 trailing closure는 정말 자주 쓰는 문법이죠.

     

    그런데 array나 dictionary 타입 뒤에는 trailing closure를 쓸 수 없다는 걸 아셨나요?

    [String] 같은 타입 뒤에 중괄호를 쓰면 파서가 제대로 인식하지 못해서 에러가 발생합니다.

    이번 SE-0508은 바로 이 제약을 풀어주는 제안입니다.

    이미 Accepted 상태이고 구현도 완료되었으니, 곧 실제로 활용할 수 있을거에요 🚀

     


    왜 필요한가?

    Array에 trailing closure를 받는 init을 정의하는 건 꽤 합리적인 디자인입니다.

     

    예를 들어 @ArrayBuilder result builder가 있다면

    extension Array {
        init(@ArrayBuilder build: () -> [Element]) {
            self = build()
        }
    }

     

    또는 nil을 반환할 때까지 element를 생성하는 init

    extension Array {
        init(generate: () -> Element?) {
            self = []
            while let element = generate() {
                append(element)
            }
        }
    }

     

    하지만 이렇게 쓸 수 없습니다

    대부분의 경우 closure를 받는 init은 trailing closure 문법으로 호출할 수 있습니다.

    그런데 Array나 Dictionary 타입에서는 파서가 이를 허용하지 않아요.

    // error: 'let' declarations cannot be computed properties
    let value = [String] {
      "a"
    }
    
    // error: variable with getter/setter cannot have an initial value
    var value = [String] {
      "a"
    }
    
    // error: closure expression is unused
    let value = [String]
    {
      "a"
    }

     

    대신 이렇게 써야 합니다.

    let value = [String].init {
      "a"
    }
    
    let value = [String]() {
      "a"
    }

     

    InlineArray는 된다?

    흥미롭게도 InlineArray에서는 trailing closure가 이미 지원됩니다:

    let powersOfTwo = [4 of Int] { index in
      1 << index
    }

     

    Array와 Dictionary에서만 안 되는 건 불필요한 제약이고, InlineArray와도 일관성이 없습니다.

     

     


    제안된 해결책

    Array 타입과 dictionary 타입 뒤에 trailing closure를 쓸 수 있게 합니다.

    let value = [String] {
      "a"
    }
    
    let value = [String: Int] {
      (key: "a", value: 42)
    }

     

    이제 이런 코드가 정상적으로 파싱되고 컴파일됩니다!

     


    어떻게 동작하나?

    현재 파싱 동작을 이해하려면 두 가지를 알아야 합니다.

     

    1. [...]는 항상 literal로 파싱됨

    표현식을 파싱할 때 [...] 토큰은 항상 array나 dictionary literal로 파싱됩니다.

    [String]이나 [String: Int] 같은 경우, 필요하면 타입 체크 단계에서 array/dictionary 타입으로 변환됩니다.

    [String]이 실제로는 let String = "a" 프로퍼티를 사용하는 단일 요소 array literal일 수도 있으니까요.

     

    2. 중괄호는 trailing closure... 단 literal 뒤는 제외

    표현식 뒤에 { 토큰이 오면 trailing closure로 해석됩니다.

    단, 이전 표현식이 literal인 경우는 제외입니다.

    Swift 5.2에서 callAsFunction이 도입되기 전(SE-0253)까지는 이게 합리적이었습니다.

    literal 뒤에 trailing closure가 올 valid한 케이스가 없었거든요.

     


    솔루션

    Array와 dictionary literal 뒤에 trailing closure를 허용하도록 변경합니다.

    이렇게 하면 이런 init(_:) trailing closure 예시가 제대로 파싱되고 컴파일됩니다.

    let value = [String] {
      "a"
    }
    
    let value = [String: Int] {
      (key: "a", value: 42)
    }

     


    추가 효과: callAsFunction도 지원

    결과적으로 callAsFunction trailing closure도 지원됩니다.

    extension Array {
        func callAsFunction<T>(mapElement: (Element) -> T) -> [T] {
            map(mapElement)
        }
    }
    
    let value = ["a", "b", "c"] {
        $0.uppercased()
    }

     

     

    작은 비용으로 언어의 표현력과 일관성이 크게 향상됩니다.

     

     


    호환성

    Source compatibility

    이 파싱 변경은 array literal 뒤에 오는 기존 closure literal의 의미를 바꿉니다.

    하지만 현재 실제로 컴파일되는 케이스는 거의 없어요.

    대부분은 closure expression is unused 에러가 납니다.

    ["a", "b", "c"] { // error: closure expression is unused
      "a"
    }
    
    ["a", "b", "c"]
    { "a" } // error: closure expression is unused

     

    에러가 안 나는 유일한 케이스

    Closure를 받는 result builder에서만 에러가 안 납니다.

    @resultBuilder
    enum FunctionArrayBuilder {
        static func buildBlock(_ components: (() -> Void)...) -> [() -> Void] {
            components
        }
    }
    
    @FunctionArrayBuilder
    var buildFunctions: [() -> Void] {
        let array = ["a", "b", "c"]
        { print(array) }
    }

    이 코드는 이제 컴파일되지 않습니다.

    하지만 이 result builder 케이스는 이미 매우 취약하고 비실용적입니다.

     


    왜 취약한가?

    • 연속된 closure literal은 세미콜론 없이 지원 안 됨
    @FunctionArrayBuilder
    var buildFunctions: [() -> Void] {
        { print("a") }
        { print("b") } // error: extra trailing closure passed in call
    }

     

    • 작은 변경으로도 컴파일이 깨짐
    // Compiles
    @FunctionArrayBuilder
    var buildFunctions: [() -> Void] {
        let array = ["a", "b", "c"]
        { print(array) };
        { print(array.count) }
    }
    // Doesn't compile
    @FunctionArrayBuilder
    var buildFunctions: [() -> Void] {
        let array = ["a", "b", "c"]
        let count = array.count
        { print(array) }; // error: cannot convert value of type '()' to closure result type 'Bool'
        { print(count) }
    }

     

    • callAsFunction이 있으면 의미가 바뀜
    extension Int {
      func callAsFunction(_ closure: () -> Void) -> Int {
        closure()
        return self
      }
    }
    
    @FunctionArrayBuilder
    var buildFunctions: [() -> Void] {
        let array = ["a", "b", "c"]
        let count = array.count
        { print(array) }; // callAsFunction trailing closure, not an accumulated result builder value
        { print(count) }
    }

     

     

    이런 케이스는 실제로 사용되는 예시가 없고, 더 복잡한 파싱 규칙으로 수용하는 것보다 이 source break를 받아들이는 게 낫다고 판단했습니다.

     

     


    ABI compatibility

    기존 선언에 대한 새로운 callsite 문법만 추가하는 거라 ABI에 영향이 없습니다.

     


    향후 방향

    모든 literal에 trailing closure 허용?

    이 제안은 array와 dictionary literal만 지원합니다.

    더 나아가서 모든 literal에 trailing closure를 허용할 수도 있어요.

    extension String {
      func callAsFunction(_ closure: (String) -> Void) {
        closure(self)
      }
    }
    
    "Hello world" { // currently, error: closure expression is unused 
      print($0)
    }

    다른 trailing closure 케이스와 더 일관되고, 특별한 단점도 없습니다.

    다만 array/dictionary만큼 강력하게 동기부여되지는 않아요.

    Array와 dictionary는 타입 표현식이 literal로 파싱되는 잠재적 모호성 때문에 특별하거든요.

     


    고려된 대안들

    추가 파싱 휴리스틱

    같은 줄에서만 허용?

    Source break는 array literal 다음 줄에 result builder closure가 오는 경우입니다.

    @FunctionArrayBuilder
    var buildFunctions: [() -> Void] {
        let array = ["a", "b", "c"]
        { print(array) }
    }

     

    같은 줄에 있으면 이미 컴파일되지 않습니다.

    @FunctionArrayBuilder
    var buildFunctions: [() -> Void] {
        let array1 = ["a", "b", "c"] { // error: cannot convert return expression of type '()' to return type '[String]'
            print(array)
        }
    
        let array2 = ["a", "b", "c"] { // error: variable with getter/setter cannot have an initial value
            ["d"]
        }
    }

    같은 줄일 때만 trailing closure로 처리하면 source break를 피할 수 있습니다.

    하지만 이건 다른 모든 brace/trailing closure 케이스와 일관성이 없어요.

     

    let array = ["a", "b", "c"].map 
    { 
      $0.uppercased()
    }
    
    if array.count >= 3
    {
      print("success: \(array)")
    }

     

     

    이런 임의적인 불일치는 코드 포매팅 도구 같은 생태계 전반에 복잡성을 추가합니다.

     

     


    Array 타입만 지원?

    Array/dictionary 타입만 지원하고 다른 literal은 제외하는 방법도 있습니다.

    [String] 타입은 valid한 표현식이 아니니까 source compatibility 문제가 없겠죠.

    하지만 파싱 시점에는 [String]이 타입인지 literal인지 알 수 없습니다.

    let String = "a"
    let array = [String] // an array literal, ["a"]
    { print(array) }

     

    단일 요소 array/dictionary literal이고 단일 identifier를 포함하는 경우만 허용할 수도 있지만, 이러면 callAsFunction 케이스가 불필요하게 제거됩니다.

     


    Conclusion

    SE-0508은 이미 Accepted되었고 구현도 완료된 상태입니다.

    개인적으로는 정말 합리적인 개선이라고 생각해요.

    InlineArray에서는 되는데 Array에서는 안 된다는 게 말이 안 됐거든요.

    @ArrayBuilder 같은 result builder와 함께 쓰면 정말 깔끔한 API를 만들 수 있을 것 같습니다.

    작은 변경이지만 언어의 일관성과 표현력을 크게 높여주는 개선이네요 😃

     


    References

     

    swift-evolution/proposals/0508-array-expression-trailing-closures.md at main · swiftlang/swift-evolution

    This maintains proposals for changes and user-visible enhancements to the Swift Programming Language. - swiftlang/swift-evolution

    github.com

     

    Support trailing closure syntax for single-argument Array and Dictionary initializers

    In the recent discussion about @ArrayBuilder, it came up that trailing closure syntax is not currently supported in the most intuitive way for Array initializers like init(@ArrayBuilder build: () -> [Element]): I investigated this on the parsing side, and

    forums.swift.org

     

    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 l

    forums.swift.org

     

    [Accepted] SE-0508: Array expression trailing closures

    Hi all, The review of SE-0508: Array expression trailing closures concluded on February 12, 2026. The language steering group has decided to accept the proposal. Feedback was positive on adding language support for trailing closures after sugared array and

    forums.swift.org

Designed by Tistory.