Swift Result Builders
A result builder is a type that gathers the results of the expressions in a block into one value. The @resultBuilder mark turns a type into such a builder.
Gathering lines from a block
LinesBuilder wraps each string into an array and then joins the arrays in order. The block of three strings becomes one array of three lines.
@resultBuilder
struct LinesBuilder {
static func buildExpression(_ expression: String) -> [String] {
return [expression]
}
static func buildBlock(_ components: [String]...) -> [String] {
return components.flatMap { part in part }
}
}
func makeLines(@LinesBuilder content: () -> [String]) -> [String] {
return content()
}
let lines = makeLines {
"Start"
"Middle"
"End"
}
print(lines)
print(lines.count)Expressions built from values
An expression in the block can be a stored string or a built string, not only a plain literal. Each expression still passes through buildExpression on its way into the array.
@resultBuilder
struct LinesBuilder {
static func buildExpression(_ expression: String) -> [String] {
return [expression]
}
static func buildBlock(_ components: [String]...) -> [String] {
return components.flatMap { part in part }
}
}
func makeLines(@LinesBuilder content: () -> [String]) -> [String] {
return content()
}
let name = "Mina"
let lines = makeLines {
"Report"
name
"Score \(90)"
}
for line in lines {
print(line)
}Joining the gathered lines
The gathered value is a plain array of strings, so it can be joined with a separator. The three colour names become one hyphen joined string.
@resultBuilder
struct LinesBuilder {
static func buildExpression(_ expression: String) -> [String] {
return [expression]
}
static func buildBlock(_ components: [String]...) -> [String] {
return components.flatMap { part in part }
}
}
func makeLines(@LinesBuilder content: () -> [String]) -> [String] {
return content()
}
let lines = makeLines {
"red"
"green"
"blue"
}
print(lines.joined(separator: "-"))A builder parameter on another function
Any function can mark a parameter with the builder attribute. The makeMenu function joins the gathered lines with a comma and a space before returning them.
@resultBuilder
struct LinesBuilder {
static func buildExpression(_ expression: String) -> [String] {
return [expression]
}
static func buildBlock(_ components: [String]...) -> [String] {
return components.flatMap { part in part }
}
}
func makeMenu(@LinesBuilder content: () -> [String]) -> String {
return content().joined(separator: ", ")
}
let menu = makeMenu {
"Soup"
"Salad"
"Bread"
}
print(menu)Example
@resultBuilder
struct LinesBuilder {
static func buildExpression(_ expression: String) -> [String] {
return [expression]
}
static func buildBlock(_ components: [String]...) -> [String] {
return components.flatMap { part in part }
}
}
func makeLines(@LinesBuilder content: () -> [String]) -> [String] {
return content()
}
let title = "Weekly"
let lines = makeLines {
title
"Tasks done"
"Tasks open"
}
print(lines.count)
print(lines.joined(separator: " | "))Key points
- The @resultBuilder mark goes before the type that does the gathering.
- buildExpression turns one expression in the block into a component value.
- buildBlock combines the component values into the final value.
- A function parameter marked with the builder attribute accepts a block of expressions.
- Calling that function runs the block through the builder and hands back the gathered value.
- In LinesBuilder each expression becomes a one-element array and the block joins the arrays in order.
In this example, the makeLines function gathers three string expressions through LinesBuilder into one array. The stored title string counts as an expression just like the two literals. The prints show the count 3 and the three lines joined by a vertical bar separator.
Note
The expressions in the block are separated by line breaks rather than by commas.
Try it Yourself
Edit the code, then press Run.
Running is simulated
Exercise
Which builder method turns one expression from the block into a component value?