Result Builders

This section documents the Swift 6 language mode as shipped by Swift 6.3, as published in The Swift Programming Language at docs.swift.org, which is the reference these pages are written and verified against. 6.4-beta-only features are always flagged as such — never presented as baseline.

This content was generated with the assistance of AI and should be verified against docs.swift.org before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

A result builder is a type annotated @resultBuilder whose static build…​ methods tell the compiler how to turn an ordinary-looking block of statements into a single built-up value — the mechanism behind SwiftUI’s @ViewBuilder, but usable for any tree- or sequence-shaped value a domain-specific block syntax should produce.

@resultBuilder and buildBlock

@resultBuilder
struct ArrayBuilder<Element> {
    static func buildBlock(_ components: Element...) -> [Element] {
        components
    }
}

@ArrayBuilder<Int>
func makeNumbers() -> [Int] {
    1
    2
    3
}
makeNumbers()   // [1, 2, 3]

Applying @ArrayBuilder<Int> to a function (or a closure parameter, via @ArrayBuilder<Int> () → [Int]) tells the compiler to rewrite every statement in its body as an argument to ArrayBuilder<Int>.buildBlock(_:) rather than evaluate them as ordinary top-level expression statements — 1; 2; 3 becomes buildBlock(1, 2, 3), whose return value is what the function actually returns.

buildOptional, buildEither and buildArray

@resultBuilder
struct StringBuilder {
    static func buildBlock(_ components: String...) -> String {
        components.joined(separator: "\n")
    }
    static func buildOptional(_ component: String?) -> String {
        component ?? ""
    }
    static func buildEither(first component: String) -> String { component }
    static func buildEither(second component: String) -> String { component }
    static func buildArray(_ components: [String]) -> String {
        components.joined(separator: "\n")
    }
}

@StringBuilder
func describe(_ n: Int) -> String {
    "n is \(n)"
    if n.isMultiple(of: 2) {
        "n is even"
    }
    if n > 0 {
        "positive"
    } else {
        "non-positive"
    }
    for i in 0..<n {
        "item \(i)"
    }
}

An if with no else compiles to buildOptional(:) (the branch’s value, or nil when it doesn’t run); an if/else (or a switch) compiles to buildEither(first:)/buildEither(second:) depending on which branch actually executed, so both branches must build to the same result type even though only one runs; a for loop compiles to buildArray(:) over the array of per-iteration results. Each is optional — a builder that never uses conditionals or loops need not implement them at all, and the compiler reports a clear diagnostic at the call site of any control-flow construct its builder doesn’t support.

buildExpression, buildPartialBlock and buildFinalResult

@resultBuilder
struct HTMLBuilder {
    static func buildExpression(_ value: String) -> String {
        value    // a hook to convert/validate each individual statement's value before it reaches buildPartialBlock
    }

    static func buildPartialBlock(first: String) -> String {
        first
    }
    static func buildPartialBlock(accumulated: String, next: String) -> String {
        accumulated + "\n" + next     // folds statements left to right instead of taking one variadic buildBlock
    }

    static func buildFinalResult(_ component: String) -> String {
        "<html>\n\(component)\n</html>"    // a last transformation applied only once, to the whole built value
    }
}

@HTMLBuilder
func page() -> String {
    "<h1>Title</h1>"
    "<p>Body</p>"
}

buildExpression(:) lets a builder validate or convert each statement’s value individually (e.g. wrapping a String into a richer node type) before it is combined with its siblings. buildPartialBlock(first:) / buildPartialBlock(accumulated:next:) is the modern (Swift 5.4+) alternative to a single variadic buildBlock: the compiler folds statements pairwise, left to right, which both avoids the combinatorial overload sets a variadic buildBlock needs to support every argument count and allows heterogeneous, per-position result types (exactly how ViewBuilder types each position of a SwiftUI body distinctly). buildFinalResult(:) applies one last transformation to the fully combined value, letting the builder’s public return type differ from the type its internal combination methods work with (here, wrapping the assembled body in <html> tags exactly once).

Building a Small DSL

@HTMLBuilder
func greeting(named name: String, isVIP: Bool) -> String {
    "<h1>Welcome, \(name)</h1>"
    if isVIP {
        "<p>You have VIP access.</p>"
    } else {
        "<p>Standard access.</p>"
    }
}
print(greeting(named: "Ada", isVIP: true))

The payoff of a result builder is exactly this: a block of ordinary Swift statements — string literals, if, for — reads like a small embedded language for the domain at hand (here, an HTML fragment builder; the same shape works for validation rules, SQL fragments, or a routing table), while every branch is still type-checked Swift code, not a string template. SwiftUI’s @ViewBuilder (an ordinary result builder over the View protocol using buildPartialBlock/buildEither/buildOptional the same way) is the reason body { …​ } closures read as a flat list of views — it is mentioned here only as the most widely recognized example of the pattern; SwiftUI itself is out of scope for this reference.

flowchart LR A["@resultBuilder body:\nexpr1\nif cond { expr2 } else { expr3 }"] --> B["buildExpression(expr1)\nbuildExpression(expr2) / buildExpression(expr3)"] B --> C["buildEither(first:) / buildEither(second:)\nfor the if/else branch"] C --> D["buildPartialBlock(first:) then\nbuildPartialBlock(accumulated:next:)\nfolding every statement together"] D --> E["buildFinalResult(_:)\n(if implemented)"] E --> F[Value returned from the\n@resultBuilder-annotated function]

The compiler performs this rewriting entirely at compile time: a builder-annotated function’s body is never executed as ordinary sequential statements, only as the chain of build…​ calls the diagram above shows, which is why a builder can support only the specific control-flow constructs whose corresponding build…​ method it actually implements.

See Also

  • Closures — @escaping/@Sendable closures, which a result-builder parameter is itself one variety of.

  • Attributes and Compiler Control — @resultBuilder alongside Swift’s other attributes.

  • Macros — a different compile-time code-transformation mechanism, generating or rewriting declarations rather than combining statement values.

  • Functional Programming — higher-order functions and composition, the style result builders are most often layered on top of.