Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt

Use this file to discover all available pages before exploring further.

The @resultBuilder attribute is a compiler feature that allows a custom type to intercept and transform a sequence of statements within a closure into a single, aggregated value. By applying this attribute to a struct, class, or enum, you define a domain-specific language (DSL) where the Swift compiler translates standard control-flow syntax into a series of static method calls on the builder type. When a closure or function is annotated with a result builder, the compiler performs an Abstract Syntax Tree (AST) transformation. It parses the sequential statements, conditionals, and loops inside the block, and replaces them with calls to specific build* methods defined on the builder type.

Builder Type Structure

A result builder is defined by applying the @resultBuilder attribute to a type and implementing at least one block-building static method (buildBlock or buildPartialBlock). The type operates entirely through static methods; it is never instantiated. To demonstrate a fully compilable implementation, the following examples rely on these concrete types:
struct Expression { let text: String }
struct Component { let text: String }
struct FinalResult { let text: String }
Here is the foundational structure of a result builder. A valid builder must contain at least one method to combine components:
@resultBuilder
struct AbstractBuilder {
    static func buildBlock(_ components: Component...) -> Component {
        let combined = components.map(\.text).joined(separator: " ")
        return Component(text: combined)
    }
}

The build Method Contract

The compiler looks for specific static methods to handle different Swift language constructs. You only need to implement the methods corresponding to the syntax you want your builder to support. The following extensions build upon the AbstractBuilder defined above to support a complete DSL.
  • buildBlock(_:) (Legacy / Variadic Combination) Translates sequential statements in a block. For homogeneous builders (where all components share the same type), a single variadic parameter (Component...) handles any number of statements without arity limits. However, for heterogeneous builders (which preserve distinct types using generics, like <C0, C1>), developers historically had to provide multiple overloaded buildBlock methods for different arities (e.g., up to 10 arguments).
extension AbstractBuilder {
    // Already defined in the base struct above:
    // static func buildBlock(_ components: Component...) -> Component
}
  • buildPartialBlock(first:) and buildPartialBlock(accumulated:next:) (Modern Combination, Swift 5.7+) Replaces the need for multiple buildBlock overloads in heterogeneous builders by folding components pairwise. The compiler passes the first component to buildPartialBlock(first:), and then iteratively passes the accumulated result and the next component to buildPartialBlock(accumulated:next:). This allows combining an arbitrary number of distinct types without artificial arity limits.
extension AbstractBuilder {
    static func buildPartialBlock(first: Component) -> Component {
        first
    }
    
    static func buildPartialBlock(accumulated: Component, next: Component) -> Component {
        Component(text: accumulated.text + " " + next.text)
    }
}
  • buildExpression(_:) (Optional) Lifts an expression into the builder’s internal Component type. If implemented, the compiler passes every raw expression through this method before passing it to the block-building methods. This allows the builder to accept types that differ from its internal representation.
extension AbstractBuilder {
    static func buildExpression(_ expression: Expression) -> Component {
        Component(text: expression.text)
    }
}
  • buildOptional(_:) (Optional) Translates if statements that do not have an else branch.
extension AbstractBuilder {
    static func buildOptional(_ component: Component?) -> Component {
        component ?? Component(text: "")
    }
}
  • buildEither(first:) and buildEither(second:) (Optional) Translates if-else statements and switch statements. The compiler wraps the execution path taken in either first or second to maintain a unified return type.
extension AbstractBuilder {
    static func buildEither(first component: Component) -> Component {
        component
    }
    
    static func buildEither(second component: Component) -> Component {
        component
    }
}
  • buildArray(_:) (Optional) Translates for...in loops. The compiler collects the results of each loop iteration into an array and passes it to this method.
extension AbstractBuilder {
    static func buildArray(_ components: [Component]) -> Component {
        Component(text: components.map(\.text).joined(separator: " "))
    }
}
  • buildLimitedAvailability(_:) (Optional) Translates if #available or if #unavailable blocks, allowing the builder to erase type information related to API availability.
extension AbstractBuilder {
    static func buildLimitedAvailability(_ component: Component) -> Component {
        component
    }
}
  • buildFinalResult(_:) (Optional) Transforms the final accumulated Component into a different return type. If implemented, this is the last method called before the closure returns.
extension AbstractBuilder {
    static func buildFinalResult(_ component: Component) -> FinalResult {
        FinalResult(text: component.text)
    }
}

Compiler Transformation Mechanics

To understand the mechanics, observe how the Swift compiler translates standard syntax into builder method calls at compile time. Because AbstractBuilder now implements buildExpression, buildEither, and buildFinalResult, the following code is fully valid. Source Code:
@AbstractBuilder
func generate(condition: Bool) -> FinalResult {
    Expression(text: "A")
    if condition {
        Expression(text: "B")
    } else {
        Expression(text: "C")
    }
}
Compiler-Transformed Code: (Note: This illustrates the transformation using buildBlock. If buildPartialBlock were implemented and preferred by the compiler, step 3 would use pairwise folding instead).
func generate(condition: Bool) -> FinalResult {
    // 1. Expressions are lifted
    let e1 = AbstractBuilder.buildExpression(Expression(text: "A"))
    
    // 2. Conditionals are evaluated
    let e2: Component
    if condition {
        let temp = AbstractBuilder.buildExpression(Expression(text: "B"))
        // The compiler ALWAYS wraps the branch statements in a buildBlock
        let block = AbstractBuilder.buildBlock(temp) 
        e2 = AbstractBuilder.buildEither(first: block)
    } else {
        let temp = AbstractBuilder.buildExpression(Expression(text: "C"))
        // The compiler ALWAYS wraps the branch statements in a buildBlock
        let block = AbstractBuilder.buildBlock(temp) 
        e2 = AbstractBuilder.buildEither(second: block)
    }
    
    // 3. The outer block is combined
    let finalBlock = AbstractBuilder.buildBlock(e1, e2)
    
    // 4. The final result is generated
    return AbstractBuilder.buildFinalResult(finalBlock)
}

Application of the Attribute

Once defined, the @resultBuilder attribute can be applied in three primary locations:
  1. Function Declarations: Applied directly to a function or method, transforming its body.
@AbstractBuilder 
func build() -> FinalResult { 
    Expression(text: "Direct Function") 
}
  1. Closure Parameters: Applied to a closure parameter in a function signature, transforming any closure passed to that function.
func acceptBuilder(@AbstractBuilder _ content: () -> FinalResult) { 
    let result = content()
    print(result.text)
}

acceptBuilder {
    Expression(text: "Closure Parameter")
}
  1. Computed Properties: Applied to a getter, transforming the property’s body.
struct Document {
    @AbstractBuilder 
    var body: FinalResult { 
        Expression(text: "Computed Property") 
    }
}
Master Swift with Deep Grasping Methodology!Learn More