Closures and errors
A block of code you can pass around, and how Swift reports a failure that a caller must deal with.
A block you can pass around
A closure is a block of code stored in a value. You write the steps between braces, keep them under a name, and call that name like a function.
The power shows when a closure is handed to another function. The map and filter tools you met earlier take exactly that kind of small block, which is why a short block of steps can stand in for the work needed on each item.
A closure has a type, written like a function’s signature without the name: (Int) -> Int. You can store one in a let, pass it as an argument, or return it from a function. When the closure is the last argument, Swift lets you write it after the call’s parentheses, which is the trailing-closure form used by map and filter.
Failure you can catch
Some work can fail for reasons the code cannot control. Swift writes that into a function with throws after the parentheses, so the caller can see that the call may not succeed.
An error itself is a value. enum ParseError: Error { case empty } names one, and throw hands it up the call chain at the moment something goes wrong.
Throwing and catching
Calling a throwing function means writing try in front of it. That word is a note to the reader that this line could fail, and the compiler insists you handle it.
A do block holds the risky calls. Each catch names an error it can handle, and a plain catch at the end is the safety net for anything else. When none is needed, nothing happens and the code carries on.
catch arms are tried in order, so a specific pattern must come before a broad one. catch let error as LoginError catches every case of that enum, while catch LoginError.empty catches one. try? turns a throwing call into an optional, discarding the error itself, and try! stops the program if it throws; both are shortcuts that hide the reason.
Closures keep the names around them
A closure keeps the names it uses from the surrounding scope. That is how a block passed to filter can read a limit defined outside it, and why changing that outside value before the call changes what the block sees.
The in keyword separates the parameter list from the body when you write the types out. Most closures in everyday Swift skip it because the compiler can infer the parameter and return types from the context.
A closure captures variables, not copies of their values, so a block that reads a var sees later changes to it. That is convenient and occasionally surprising when the closure outlives the function that made it. A closure stored for later use must be marked @escaping in the parameter list, which is the compiler asking you to confirm that it will still be alive after the call returns.
What goes wrong
Forgetting try is a compile error at the call, which is the easy case. The harder mistake is catching too much: a plain catch at the top swallows every failure, including the one you meant to fix, and the program carries on with a value it never produced. Put the specific arms first and keep the broad one last, where it is visibly the fallback.
A do block protects only the calls written inside it. A throwing call in the condition of an if, or in a closure passed elsewhere, needs its own handling. And try? returning nil is not the same as a function returning nil; the first means the call failed, the second means it succeeded with an empty answer.
A closure, and a call that can fail
let double = { (n: Int) -> Int in
return n * 2
}
print(double(4))
enum SizeError: Error {
case tooSmall
}
func check(_ side: Int) throws -> Int {
if side < 1 {
throw SizeError.tooSmall
}
return side
}
do {
print(try check(4))
} catch {
print("bad size")
}Three mistakes stop a failure from being handled. A call to a throwing function needs try in front of it, and forgetting it is a compile error at the call. Put the specific catch arms before the plain one, because the first match handles the error. And a do block only guards the calls written inside it; an error thrown before or after it still travels up.
Checking a list of scores
Run it and read which scores are accepted and which are reported. Then add a score of 150 and predict the new line before you run again.
Try these next
- Write a closure that triples a number, store it in a
let, and pass it to a function. - Make an error with an associated value and print that value in its
catcharm. - Add a second failure case to a throwing function and handle each case separately.
A closure and a name check
Run it and read the three answers. Then change the short name so it passes, and predict which `catch` arm stops running.
A throwing function is a promise that a failure is possible. When you call one, decide in the same breath what should happen if it fails, rather than letting the error travel further than it needs to.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What is a closure?
