Swift 2 – Error Handling

Apple did announce Swift 2.0 at WWDC 2015 yesterday. Here are some links:

Lot of nice changes, but some of them are weird. Especially error handling.

Swift 1.2 pattern

An example of error handling pattern in Swift < 2:

enum Result<T, E> {
case Success(T)
case Failure(E)
}

func doSomeMagic() -> Result<Int, NSError> {
return .Success(100)
// return .Failure with NSError if it fails instead of .Success(Int)
}

Pretty straightforward and readable.

Swift 2.0

New version of Swift introduces weird error handling IMO. Here’s an example. Lets create ATM class with withdraw function:

enum ATMError: ErrorType {
case InsufficientFunds(required: Double, available: Double)
case ConnectionFailed(description: String)
case InternalError(description: String)
}

class ATM {
var disposable: Double?

func open() throws -> Void {
// open connection and fetch disposable amount
self.disposable = 100

// or throw an error
// throw ATMError.ConnectionFailed(reason: "Whatever error")
}

func close() -> Void {
// close connection
}

func withdraw(amount: Double) throws -> Void {
if let disposable = self.disposable {
if amount > disposable {
throw ATMError.InsufficientFunds(required: amount, available: disposable)
}
self.disposable! -= amount
} else {
throw ATMError.InternalError(description: "Disposable amount not set")
}
}
}

What’s new? throws keyword in withdraw function declaration. It basically
says that this function can throw an error. But you can’t specify which error it can
throw. Readability, safety, … don’t get it why. Anyway, next step is to handle these
errors.

let atm = ATM()
do {
try atm.open()
defer {
atm.close()
}
try atm.withdraw(20)
print(atm.disposable)
}
catch ATMError.ConnectionFailed(let description) {
print("Connection failed: (description)")
}
catch ATMError.InsufficientFunds(let required, let available) {
print("Not enough funds, available: (available), required: (required).")
}
catch ATMError.InternalError(let description) {
print("Internal error: (description)")
}

Verbosity, verbosity, …

  • Every call which can throw must be marked with try. You are going to get Call can throw but is not marked with ‘try’ error if it is not marked properly.
  • To scope your catch you have to use do. Why not try instead of
    do and omit try mark in calls? Who knows. Probably because of try!
    (see below).

Next new thing is defer. This block of code is executed at the end of the scope (error thrown, return, …). You’re not forced to use it with do, try, …
only. It’s a general statement which can be used in this way as well:

func doSomething(filename) {
let f = open(filename)
defer {
close(f)
}
while let line = try file.readline() {
}
// defer code block called here
}

Put verbosity aside for now. Remove last catch with .InternalError
error and try to compile it. It succeeds. Hmmm. It shouldn’t. At least warning
that some errors are not handled would be nice. Or force me to use:

catch {
print("Ooops")
}

We’re humans, we make mistakes, … Do you remember how long we had to wait for
NS_ENUM in Objective-C, so, clang can provide some useful warnings?

Passing errors is pretty straightforward.

enum Exc: ErrorType {
case Oops
}

func bad() throws -> Void {
throw Exc.Oops
}

func doSomething() throws -> Void {
try bad()
}

Just use throws keyword in doSomething() declaration and don’t forgot to
use try. Things are going to be weird with try!. What’s this? You’re saying
that you do know that error will not be thrown in this case and then you can omit error
handling:

func bad(t: Bool = false) throws -> Void {
if t {
throw Exc.Oops
} else {
print("Tralala")
}
}

try! bad() // OK
try! bad(true) // SIGABRT

Things are going to be more messy. I’m a good citizen and would like to handle
errors in higher level because of this try! in my code or for whatever reason.

func good() {
try! bad(true)
}

do {
good()
}
catch Exc.Oops {
// In case it really throws and I made a mistake
print("Never ever happen")
}

No way, warning: ‘catch’ block is unreachable because no errors are thrown in ‘do’
block
. Hmm, should try with try.

do {
try good()
}
catch Exc.Oops {
// In case it really throws and I made a mistake
print("Never ever happen")
}

No way. Same warning plus new one: No calls to throwing functions occur within ‘try’
expression
. Hmm, should try with throws.

func good() throws {
try! bad(true)
}

do {
try good()
}
catch Exc.Oops {
// In case it really throws and I made a mistake
print("Never ever happen")
}

No warnings, yes! Compile, run and SIGABRT. What? Don’t use try! at all.
Because if try! whatever() call throws an error, there’s no way to handle it
and it always ends up with SIGABRT.

Swift is fast and safe. But it looks like that safety was sacrificed for speed in this
case. We have to wait for source code of Swift compiler to get reasoning behind this
decision and to get an answer why it was done in this way.

It looks weird to me for now and I don’t like it. I don’t like exceptions at all (wonder
why they call them errors in Swift 2.0) and I hope that Apple is not going to use them heavily in upcoming frameworks.

Sticking with enum Result<T, E>