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:
@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:
@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:
static func buildBlock<T>(_ components: T...) -> [T]
2. buildOptional
Handles optional statements (if statements without an else):
static func buildOptional<T>(_ component: T?) -> T?
3. buildEither
Handles conditional statements (if-else):
static func buildEither<T>(first component: T) -> T
static func buildEither<T>(second component: T) -> T
4. buildArray
Transforms arrays or variadic expressions:
static func buildArray<T>(_ components: [T]) -> [T]
5. buildExpression
Transforms individual expressions:
static func buildExpression<T>(_ expression: T) -> T
Let's create a more complete result builder that supports conditionals:
@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:
@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:
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:
@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:
@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:
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:
@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:
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
- Swift Evolution Proposal (SE-0289)
- Apple's Swift Documentation on Result Builders
- WWDC 2021 Session: Meet async/await in Swift (includes information about result builders)
Exercises
- Create a result builder that constructs a mathematical expression and evaluates it.
- Extend the HTML builder to support more HTML tags and attributes.
- Build a SQL query builder using result builders.
- Create a result builder that generates Markdown text.
- 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! :)