Errors and throw
What you'll learn:
- declare an error type, throw an error with
throwand catch it indo/catch; - pass an error on through several functions and catch its separate cases;
- turn an error into
nilwithtry?and explain whentry!crashes the program; - clean up on any way out of a function with
defer.

Picture a post office where parcels ride a belt from counter to counter. The first counter takes the parcel in, and the last one reads the address and sends the parcel on. But sometimes the address cannot be read. The clerk does not guess and does not toss the parcel into a corner: they stamp it "return to sender", and the belt carries it back. A net is stretched at the first counter, and the parcel lands in it.
The stamp tells the sender at once what went wrong, and they decide what to do next. Functions in a program can do the same. A function that could not do its job hands back an error instead of a result. The error rides back to whoever called the function until someone catches it.
Why is this needed if there are optionals? From the optionals theme you know that Int("42") gives an Int?, and text that is not all digits gives nil. But nil does not say what exactly is wrong:
let empty = Int("")
let letters = Int("4x2")
print(empty == nil, letters == nil) // true trueBoth times the result is nil. Yet the troubles differ: in the first case the text is empty, in the second it has a letter. An optional is a box that may be empty, but you cannot put a note with the reason inside. To report the reason, Swift has errors.
The error type and throw
An error in Swift is a value of a type that conforms to the Error protocol. Most often that type is an enum: each of its cases names one reason for a failure. Unlike a compile error, such an error appears while the program is already running. Let's write a function parse that reads a number from text and reports why it could not:
enum ParseError: Error {
case empty
case badFormat
}
func parse(_ text: String) throws -> Int {
if text.isEmpty {
throw ParseError.empty
}
guard let n = Int(text) else {
throw ParseError.badFormat
}
return n
}Let's go through the program line by line:
enum ParseError: Errordeclares an enum with two reasons:emptymeans the text is empty,badFormatmeans the text is not a number.: Errorafter the name is the conformance to theErrorprotocol.- The word
throwsafter the parameters says that the function can throw an error. It stands before the arrow->. throw ParseError.emptythrows an error if the text is empty.throwleaves the function likereturndoes, so it also fits theelseof aguard.- If both checks pass,
return nreturns the number as usual.
To throw an error means to end the function at once and, instead of a result, hand an error to whoever called the function. The lines below throw no longer run. If you remove throws from the header of parse, the lines with throw do not compile. The compiler requires you to say in advance that the function can hand back an error.
Interviewers like to ask this: what is Error? The answer:
Erroris an empty protocol: it has no property or method requirements, so an enum needs only: Errorafter its name;- conforming to
Erroris what allows a value to be thrown withthrow; - errors are usually described by an enum, and its cases name the reasons for a failure.
Catching an error: do, try, catch
A function with throws is called differently from an ordinary one. You write try before the call and put the call itself in a do block, followed by catch:
do {
let n = try parse("42")
print(n + 1) // 43
print(try parse("4x2"))
print("done")
} catch {
print("error:", error) // error: badFormat
}Let's walk through the program:
do { … }is a block inside which an error may arrive.trymarks a call that can throw an error. It is also written inside an expression:print(try parse("4x2")).parse("42")returns42, and the program prints43.parse("4x2")throwsParseError.badFormat. The rest of thedoblock is skipped: neither theprintwith this call norprint("done")runs.- Execution moves to
catch. Inside it, the caught error is in the constanterror, andprintshows its case:badFormat. - After
catch, the program goes on to the lines below it and does not return todo.
To catch an error means to take it in catch and decide what to do: print a message, put in a value or ask for the text again. A thrown error is caught by the nearest catch on its way back; here it is the one right after do. If there was no error, catch does not run at all.
Here the post office picture is off. A clerk may put a hard parcel aside and come back to it later. A function does not go back to work after throw: the lines below throw will not run, and the call will not return any value.
Common mistake
A function with throws was called without try:
do {
let k = parse("7") // ✗
print(k)
} catch {
print(error)
}error: call can throw but is not marked with 'try'The program does not compile. The message says: the call can throw an error, but it is not marked with try. The compiler requires try on every such call, so that the code shows where the program may turn off into catch. The fix is let k = try parse("7").
Key points
- An error is a value of a type that conforms to the
Errorprotocol; usually it is an enum with the reasons for a failure. - A function with
throwsthrows an error withthrowand ends at once, without a result. - A function with
throwsis called withtryinsidedo: on an error the rest ofdois skipped andcatchruns, where the error is in the constanterror. Erroris an empty protocol: for an enum,: Errorafter its name is enough to conform.