Closures
A closure is a piece of code written in braces that you can store in a
variable, pass to a function, and run later. You have already seen them in
calls like list.filter { n in n > 0 } and Button("Save") { save() }.
let double: fn(Int) -> Int = { n in n * 2 }print(double(21))42Syntax
Section titled “Syntax”A closure is written in braces. If it takes parameters, their names come
first, followed by in:
let add: fn(Int, Int) -> Int = { a, b in a + b }let sayHi: fn() = { print("hi") }{ a, b in … }takes two parameters,aandb.{ … }with nointakes no parameters.- Use
_for a parameter you don’t need:{ _ in "same" }.
The body works like a function body: it can have several lines, and its
last line is the result. return inside a closure returns from the
closure, not from the function around it.
let describe: fn(Int) -> String = { n in if n < 0 { return "negative" } let size = if n > 100 { "big" } else { "small" } "a {size} number"}print(describe(7))a small numberTypes come from context
Section titled “Types come from context”Parameter types aren’t written inside the closure. Tessel takes them from where the closure is used: the function type of the variable it’s stored in, or the parameter it’s passed to.
let numbers = [1, 2, 3]let squares = numbers.map { n in n * n } // n is an Int, because numbers is [Int]On its own, a closure with parameters has no types to go on, so this is an error:
let increment = { n in n + 1 }error: can't tell the types of this block's parameters = help: pass it where a function is expected, or give it a type: `let f: fn(Int) -> Int = { x in … }`Add a type to the variable, let increment: fn(Int) -> Int = { n in n + 1 },
and it works. A closure without parameters can be stored without a type.
Function types
Section titled “Function types”A function type is written fn(ParameterTypes) -> ReturnType. Leave off
-> … for a function that returns nothing.
| Type | Example closure |
|---|---|
fn() | { print("done") } |
fn(String) | { text in print(text) } |
fn(Int) -> Bool | { n in n > 0 } |
fn(Int, Int) -> Int | { a, b in a + b } |
Closures and named functions both have function types, so a function name can be used wherever a closure can:
fn isEven(n: Int) -> Bool { n % 2 == 0}
fn main() { let check: fn(Int) -> Bool = isEven print(check(4)) print([1, 2, 3, 4].filter(isEven).count)}true2Calling a function value never uses labels: check(4), not
check(n: 4).
Function values can go in lists, be returned from functions, and be
optional (fn()?). An optional function is unwrapped with if let:
let onFinish: fn()? = nilif let finish = onFinish { finish()} else { print("nothing to do")}To only check whether one was given, compare it with nil:
onFinish == nil or onFinish != nil.
Passing closures to functions
Section titled “Passing closures to functions”A parameter with a function type accepts a closure:
fn measure(label: String, work: fn() -> Int) { print("{label}: {work()}")}
fn main() { measure(label: "answer", work: { 6 * 7 })}answer: 42Trailing blocks
Section titled “Trailing blocks”When the closure is the last argument, it can go after the parentheses. If there are no other arguments, the parentheses can be left out too:
fn measure(label: String, work: fn() -> Int) { print("{label}: {work()}")}
fn twice(action: fn()) { action() action()}
fn main() { measure(label: "answer") { 6 * 7 } twice { print("hello") }}answer: 42hellohelloBuilt-in methods that take a single closure, like filter { … } and
map { … }, are usually called this way. Views use the same rule, which is why
Button("Add") { … } and VStack { … } look the way they do.
Capturing variables
Section titled “Capturing variables”A closure can use the variables around it. It doesn’t take a copy of them: it
uses the variables themselves. If a variable changes after the closure is
made, the closure sees the new value, and a closure can change a var it
uses:
var total = 0let addFive = { total += 5 }addFive()addFive()print(total)10A captured variable stays alive as long as a closure uses it, even after the function that declared it has returned. This lets a function hand back a closure with its own private state:
fn makeCounter() -> fn() -> Int { var count = 0 { count += 1 count }}
fn main() { let next = makeCounter() print(next()) print(next()) print(next())}123Each call to makeCounter makes a new count, so two counters don’t affect
each other.
Closures in views
Section titled “Closures in views”In an app, closures are how you say what should happen when something
occurs: Button("+") { count += 1 }, .onTap { … }, or
after(seconds: 2) { … }. They capture the view’s state, so changing it in
the closure updates the screen. See State and bindings.