Skip to content
Lesson 3 of 7

Step 1 of 4 · Reading · ~2 min

Learn

Builders and Opaque Types

Result builders are the compiler trick behind SwiftUI's view DSL. Written out, this block is not an array literal and not a series of ignored expressions:

VStack {
    Text("Hello")
    Image("icon")
}

Each statement in the closure is collected and handed to a builder type, which combines them into one value. The closure never "returns" anything — the compiler rewrites it into a call chain for you.

The attribute has two spellings, and your grader takes the older one

The feature shipped as the underscored @_functionBuilder in Swift 5.1 and was renamed to @resultBuilder when it was formally accepted in Swift 5.4. This course's grader is Swift 5.2, so:

@_functionBuilder      // builds here
@resultBuilder         // error: unknown attribute 'resultBuilder'

That is not a typo in the exercise. Real code you write today on a current toolchain uses @resultBuilder; the semantics below are identical under either spelling.

Defining a builder

@_functionBuilder
struct StringBuilder {
    static func buildBlock(_ components: String...) -> String {
        return components.joined(separator: " ")
    }
}

func sentence(@StringBuilder _ build: () -> String) -> String { build() }

let s = sentence {
    "Hello,"
    "world!"
}
print(s)                 // Hello, world!

buildBlock is the only required method. Marking the parameter @StringBuilder is what makes the caller's closure a builder block; marking a function declaration applies it to that function's own body instead.

The other build methods, and what each one unlocks

You implement only what your DSL needs — each method switches on a piece of Swift syntax inside the block:

  • buildBlock(_:) — required; merges the statements of a block.
  • buildOptional(_:) — enables a bare if with no else.
  • buildEither(first:) / buildEither(second:) — both are needed to enable if/else.
  • buildArray(_:) — enables for ... in loops.
  • buildExpression(_:) — pre-processes each individual expression, letting a block accept types other than the final result type.
  • buildFinalResult(_:) — one last transform on the way out.

Leave buildEither out and an if/else inside the block is a compile error, not a silent no-op. That is the usual first surprise: the block looks like ordinary Swift, but only the constructs your builder supports are legal in it.

Where they are used

SwiftUI views are the famous case; Swift's Regex Builders (5.7+), Vapor and Plot's HTML builders, and most test-matcher DSLs are built the same way. The pattern earns its keep whenever a caller should describe what they want as a list of declarations rather than assembling it imperatively.

Your exercise

You will write a builder whose buildBlock joins its parts with a single space, then use it on a function parameter. The mistake the grader catches is the separator: joining with "" or with a comma produces output that does not match, and remember the attribute must be @_functionBuilder to compile here.

Up nextOpaque Types and `some`Builders and Opaque Types

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…