Skip to main content

Swift Result Builders

Introduction

Swift Result Builders (previously known as Function Builders) are a powerful language feature introduced in Swift 5.4 that allows developers to create domain-specific languages (DSLs) for constructing nested data structures in a declarative way. If you've used SwiftUI, you've already experienced result builders in action through the ViewBuilder that enables SwiftUI's declarative syntax.

Result builders transform a sequence of statements into a single result value, making code that builds complex nested structures more readable and intuitive. They're particularly useful when you want to create a fluent, declarative API for your library or framework.

In this guide, we'll explore what result builders are, how they work, and how you can create your own custom result builders for your Swift applications.

Understanding Result Builders

At their core, result builders are a compile-time feature that transforms a sequence of statements into a single result. This transformation happens through a series of methods that you define in a type marked with the @resultBuilder attribute.

Basic Concepts

A result builder is a type annotation that tells the Swift compiler how to transform code written in a specific context into a result value. The transformation is done through a set of static methods defined in the result builder type.

Here's a simple example of a result builder that builds arrays:

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

This basic result builder can transform a sequence of expressions into an array. Let's see it in action:

swift
@ArrayBuilder
func buildArray() -> [Int] {
1
2
3
}

let array = buildArray()
print(array) // Output: [1, 2, 3]

In the example above, the @ArrayBuilder attribute tells the compiler to use our ArrayBuilder type to transform the body of the function. The function appears to return individual integers, but the result builder converts these expressions into an array.

Core Methods of a Result Builder

A result builder can implement several methods to handle different code structures. Here are the most important ones:

1. buildBlock

The most fundamental method, buildBlock, combines the results of all expressions in a block into a single result:

swift
static func buildBlock<T>(_ components: T...) -> [T]

2. buildOptional

Handles optional statements (if statements without an else):

swift
static func buildOptional<T>(_ component: T?) -> T?

3. buildEither

Handles conditional statements (if-else):

swift
static func buildEither<T>(first component: T) -> T
static func buildEither<T>(second component: T) -> T

4. buildArray

Transforms arrays or variadic expressions:

swift
static func buildArray<T>(_ components: [T]) -> [T]

5. buildExpression

Transforms individual expressions:

swift
static func buildExpression<T>(_ expression: T) -> T

Let's create a more complete result builder that supports conditionals:

swift
@resultBuilder
struct StringBuilder {
static func buildBlock(_ components: String...) -> String {
components.joined(separator: "\n")
}

static func buildEither(first component: String) -> String {
component
}

static func buildEither(second component: String) -> String {
component
}

static func buildOptional(_ component: String?) -> String {
component ?? ""
}
}

Now we can use this builder to create strings with conditionals:

swift
@StringBuilder
func greeting(name: String, includeTime: Bool) -> String {
"Hello, \(name)!"

if includeTime {
"The time is \(Date())"
}
}

print(greeting(name: "Swift Developer", includeTime: true))
// Output:
// Hello, Swift Developer!
// The time is 2023-06-15 14:30:45 +0000

Real-World Example: HTML Builder

Let's create a practical example: a result builder for generating HTML. This can be useful for server-side Swift applications or for generating HTML emails.

First, let's define a simple HTML element type:

swift
struct HTMLElement {
let tag: String
let attributes: [String: String]
let content: String

init(_ tag: String, attributes: [String: String] = [:], content: String = "") {
self.tag = tag
self.attributes = attributes
self.content = content
}

var description: String {
let attributeString = attributes.isEmpty ? "" : " " + attributes
.map { key, value in "\(key)=\"\(value)\"" }
.joined(separator: " ")

if content.isEmpty {
return "<\(tag)\(attributeString)>"
} else {
return "<\(tag)\(attributeString)>\(content)</\(tag)>"
}
}
}

Now, let's create our HTML builder:

swift
@resultBuilder
struct HTMLBuilder {
static func buildBlock(_ components: HTMLElement...) -> HTMLElement {
let content = components.map { $0.description }.joined()
return HTMLElement("div", content: content)
}

static func buildExpression(_ expression: HTMLElement) -> HTMLElement {
expression
}

static func buildExpression(_ expression: String) -> HTMLElement {
HTMLElement("span", content: expression)
}

static func buildOptional(_ component: HTMLElement?) -> HTMLElement {
component ?? HTMLElement("div")
}

static func buildEither(first component: HTMLElement) -> HTMLElement {
component
}

static func buildEither(second component: HTMLElement) -> HTMLElement {
component
}
}

With our builder in place, we can now create HTML in a declarative way:

swift
@HTMLBuilder
func createPage(title: String, showFooter: Bool) -> HTMLElement {
HTMLElement("html", content: """
<head>
<title>\(title)</title>
</head>
""")

HTMLElement("body") {
HTMLElement("h1", content: title)

"This is a paragraph of text."

HTMLElement("ul") {
HTMLElement("li", content: "Item 1")
HTMLElement("li", content: "Item 2")
HTMLElement("li", content: "Item 3")
}

if showFooter {
HTMLElement("footer", content: "© 2023 Swift Developer")
}
}
}

let page = createPage(title: "My Swift Page", showFooter: true)
print(page.description)
// Output: HTML page with title, list, and footer

This example shows how result builders can make code that builds complex structures (like HTML) much more readable and maintainable.

SwiftUI and Result Builders

The most famous use of result builders in Swift is SwiftUI's ViewBuilder. Let's look at how SwiftUI leverages result builders:

swift
import SwiftUI

struct ContentView: View {
@State private var showDetails = false

var body: some View {
VStack {
Text("Hello, Swift Result Builders!")
.font(.headline)

Button("Toggle Details") {
showDetails.toggle()
}

if showDetails {
Text("These are the details")
.padding()
.background(Color.yellow)
}
}
.padding()
}
}

In this SwiftUI example, the VStack initializer uses a result builder (ViewBuilder) to transform the content inside the curly braces into a collection of views.

Advanced Usage: Custom Parameters

Result builders can also be used with generic parameters to create more flexible DSLs. Here's an example of a result builder that builds a list of items with custom parameters:

swift
@resultBuilder
struct ListBuilder<Item> {
static func buildBlock(_ components: Item...) -> [Item] {
components
}

static func buildOptional(_ component: [Item]?) -> [Item] {
component ?? []
}

static func buildEither(first component: [Item]) -> [Item] {
component
}

static func buildEither(second component: [Item]) -> [Item] {
component
}

static func buildArray(_ components: [[Item]]) -> [Item] {
components.flatMap { $0 }
}
}

func makeList<T>(@ListBuilder<T> content: () -> [T]) -> [T] {
content()
}

Now we can use this to build any kind of list:

swift
let numbers = makeList {
1
2
3

if Bool.random() {
4
5
}

for i in 6...8 {
i
}
}

print(numbers) // Output: A list of integers (varies based on the random condition)

Performance Considerations

While result builders make code more readable and maintainable, they can introduce some overhead at compile time. The Swift compiler needs to transform the code, which can increase compilation times for complex builders. However, at runtime, there's typically minimal performance impact.

Summary

Swift Result Builders are a powerful feature that enables the creation of domain-specific languages for building complex nested data structures. They work by transforming a sequence of statements into a single result, making your code more declarative and easier to read.

Key takeaways:

  • Result builders transform multiple statements into a single result
  • They use specific static methods like buildBlock, buildOptional, etc.
  • Result builders power SwiftUI's declarative syntax
  • You can create custom result builders for your own domain-specific languages
  • They make complex nested structure building more readable and maintainable

Result builders are particularly useful when you want to create fluent, declarative APIs that feel like a natural part of the Swift language.

Additional Resources

Exercises

  1. Create a result builder that constructs a mathematical expression and evaluates it.
  2. Extend the HTML builder to support more HTML tags and attributes.
  3. Build a SQL query builder using result builders.
  4. Create a result builder that generates Markdown text.
  5. Implement a result builder for constructing regular expressions in a readable way.


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)