Vocabulary for this course

The terms the chapters assume, in plain words. You can open this from any chapter.

array
An ordered list of values of one type, reached by position starting at zero.
block
A group of lines of code that run together, often inside curly brackets.
closure
A piece of code you can store in a value and pass to other code, remembered with the names around it.
compiler
The program that turns the code you write into something the machine can run, reporting mistakes before it does.
dictionary
A collection of key and value pairs, where each key appears once and finds its value.
enum
A type whose value is one of a fixed set of named cases, which may each carry extra data.
expression
A piece of code that produces a value, such as a sum or a call to a function.
function
A named piece of code that takes values and returns a result when called.
guard
A check that must pass before the code after it runs, leaving the scope early when it fails.
method
A function that belongs to a type and is called on a value of that type.
nil
The absence of a value, allowed only where a type says a value may be missing.
operator
A symbol such as plus or equals that combines values into a new one.
optional
A type that either holds a value or holds nothing, written with a question mark after the type.
optional chaining
Writing a question mark before something you reach into, so the result is nil when the value is missing.
property
A named value that belongs to a type or to one of its values.
protocol
A list of requirements that a type promises to meet, so code can work with any type that meets them.
scope
The region of code where a name can be used before it goes out of reach.
string interpolation
Putting a value inside text with a backslash and parentheses, so the value becomes part of the text.
struct
A type that groups related values and makes a copy whenever it is given another name or passed along.
subscript
A way to read or write an item in an array or dictionary by putting a key or position in square brackets.
type
What kind of value something is, such as a whole number or text, and what it can do.
unwrap
To get the value out of an optional after checking that something is there.

Swift from the ground up

Closures and errors

A block of code you can pass around, and how Swift reports a failure that a caller must deal with.

Part 11 of 12

Advanced19 min readBack to Courses

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.

Output
Click "Run" to execute your code

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 catch arm.
  • 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.

Output
Click "Run" to execute your code

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?