Swift 2 – Error Handling Revisited

I wasn’t quite happy with new error handling in Swift 2 (read my last post). Changed my opinion little bit after few
days of usage and here’s why.

When I saw try, catch, … for the first time I was shocked – exceptions? Noo! But when
you look closer, it looks similar to exceptions, but nothing more. Forget about exceptions. It’s just
another way to propagate errors.

Skipping pre Result<T,E> era. Here’s an example with Result<T,E> where E is NSError.

let MyErrorDomain = "domain"
var MyErrorCancelled: Int { return -10 }

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

func doSomething() -> Result<Int, NSError> {
return Result.Failure(NSError(domain: MyErrorDomain, code: MyErrorCancelled, userInfo: nil))
}

func check() {
let result = doSomething()

switch result {
case .Success(_):
print("Success")
// Do something with result and check for another error

case .Failure(let error):
if error.domain == MyErrorDomain {
switch(error.code) {
case MyErrorCancelled:
print("Failed with MyErrorCancelled")
default:
print("Failed with unknown error code")
}
} else {
print("Failed in other domain")
}
// Add some spaghetti error handling here
}
}

Actually, in this way we have lot of error handling in our logic, which makes it unclear
what it exactly does. We can forget to handle another error codes in switch statements, etc.
Lets improve it with ErrorType.

enum MyError: ErrorType {
case Cancelled
}

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

func doSomething() -> Result<Int, MyError> {
return Result.Failure(.Cancelled)
}

func check() {
let result = doSomething()

switch result {
case .Success(_):
print("Success")

case .Failure(let error):
switch(error) {
case .Cancelled:
print("Failed with MyError.Cancelled")
}
}
}

Cleaner and we get exhaustive switch statement. Nice. But imagine you have more doSomething() functions
in your logic and you have to handle errors from both functions. You’ll end up with something like this.

enum MyError: ErrorType {
case Cancelled
case Internal
}

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

func doSomething() -> Result<Int, MyError> {
return Result.Success(10)
}

func doSomethingWithResult(result: Int) -> Result<Int, MyError> {
return Result.Failure(.Internal)
}

func check() {
var result = doSomething()

switch result {
case .Success(let value):
result = doSomethingWithResult(value)

switch result {
case .Success(_):
print("Success")

case .Failure(let error):
switch error {
case .Cancelled:
print("Failed with MyError.Cancelled")
case .Internal:
print("Failed with MyError.Internal")
}
}

case .Failure(let error):
switch error {
case .Cancelled:
print("Failed with MyError.Cancelled")
case .Internal:
print("Failed with MyError.Internal")
}
}
}

What check() function does? Hard to say in this error handling mess. Lets improve MyError and leverage
guard as well.

enum MyError: ErrorType {
case Cancelled
case Internal
}

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

var value: T? {
switch self {
case .Success(let value):
return value
case .Failure(_):
return nil
}
}

var error: E? {
switch self {
case .Success(_):
return nil
case .Failure(let error):
return error
}
}
}

func doSomething() -> Result<Int, MyError> {
return Result.Success(10)
}

func doSomethingWithResult(result: Int) -> Result<Int, MyError> {
return Result.Failure(.Internal)
}

func check() {
var result = doSomething()

guard let value = result.value else {
switch result.error! {
case .Cancelled:
print("Failed with MyError.Cancelled")
case .Internal:
print("Failed with MyError.Internal")
}
return
}

result = doSomethingWithResult(value)

guard let value2 = result.value else {
switch result.error! {
case .Cancelled:
print("Failed with MyError.Cancelled")
case .Internal:
print("Failed with MyError.Internal")
}
return
}

// Do something with value2
print(value2)
}

Much better. But still not good enough. Still hard to say what this check() is all about. You
can collapse guard statements in Xcode, but why should you do this? There’s better way.

enum MyError: ErrorType {
case Cancelled
case Internal
}

func doSomething() throws -> Int {
return 10
}

func doSomethingWithResult(result: Int) throws -> Int {
throw MyError.Internal
}

func check() {
do {
var result = try doSomething()
result = try doSomethingWithResult(result)
// Do something with final result value
print(result)
}
catch MyError.Cancelled {
print("Failed with MyError.Cancelled")
}
catch MyError.Internal {
print("Failed with MyError.Internal")
}
catch {
print("Mandatory exhaustive catch")
}
}

This way rocks, because:

  • we have error handling in one place,
  • we can easily check what check() is all about,
  • function signatures are much shorter and nicer.

What about cleanup? I saw solutions like this …

func check() {
let cleanup = {
print("Cleanup")
}
do {
let _ = try doSomething()
cleanup()
}
catch let error as NSError {
cleanup()
print(error.domain) // "MyError"
print(error.code) // 1 (.Busy)
}
catch {
cleanup()
print("Mandatory exhaustive catch")
}
}

… which has some flaws. You can forget to call it in some places, you can endup with if hell if you’re
checking what to clean up, what not, … Solution is to use defer statement, which is executed at the end
of the current block of code (scope exit).

func check() {
do {
defer {
// This is going to be executed after `print(result)`
// or if error is thrown -> a.k.a. always
print("Cleanup")
}
var result = try doSomething()
result = try doSomethingWithResult(result)
// Do something with final result value
print(result)
}
catch MyError.Cancelled {
print("Failed with MyError.Cancelled")
}
catch MyError.Internal {
print("Failed with MyError.Internal")
}
catch {
print("Mandatory exhaustive catch")
}
}

Nice, because we have cleanup logic in one place. You can also use several defer statements and all
of them are going to be executed.

func check() {
do {
defer {
// Clean up 1
}
var result = try doSomething()
defer {
// Clean up 2
}
result = try doSomethingWithResult(result)
...
}
catch MyError.Cancelled {
print("Failed with MyError.Cancelled")
}
catch MyError.Internal {
print("Failed with MyError.Internal")
}
catch {
print("Mandatory exhaustive catch")
}
}

To be more precise, not all of them. If doSomething() throws, only // Clean up 1 will be executed. If
doSomething() doesn’t throw, // Clean up 1 and // Clean up 2 will be executed.

Write short functions or you can end up with defer mess (similar to error handling without throw).

As I wrote, I changed my opinion. New error handling wins. It produces nicer, shorter
and easier to read code.

Also started to like try annotation. I take it as a reminder – Hey, here it can fail. Which
makes it easier to read, debug, …

But what if thrown error is not fatal one? Like MyError.Busy, which can mean – wait for a while
and try again. It’s sad, because you’ll end up with error handling on several levels again. It
would be nice to have something like this with guard.

guard let result = try doSomething() catch MyError.Busy {
print("Okay, this is not fatal, wait and try again")
// Rest of errors are not handled here, but still propagated
}

What’s nice is that you can cast your error to NSError, which makes some error handling
simpler and less confusing.

enum MyError: ErrorType {
case Cancelled // 0
case Busy // 1
}

func doSomething() throws -> Int {
throw MyError.Busy
}

func check() {
do {
let _ = try doSomething()
}
catch let error as NSError {
print(error.domain) // "MyError"
print(error.code) // 1 (.Busy)
}
catch {
print("Mandatory exhaustive catch")
}
}

Hmmm, nice, but I don’t like 0, 1, … Would like to provide my own numbers for error codes. Lets
try it with raw values.

enum MyError: Int, ErrorType {
case Cancelled = -1 // error.code = 0
case Busy = -2 // error.code = 1
}

func doSomething() throws -> Int {
throw MyError.Busy
}

func check() {
do {
let _ = try doSomething()
}
catch let error as NSError {
print(error.domain) // "MyError"
print(error.code) // 1 (.Busy)
}
catch {
print("Mandatory exhaustive catch")
}
}

Nice try, but it doesn’t work. Still 0, 1. Maybe we can do something with it via ErrorType protocol, which
is declared as:

protocol ErrorType {
}

Ooops, nothing there, we can’t. Sad. Maybe it’s possible, but I didn’t find how yet. Question is if it
makes sense.

This new error handling still has some flaws, but overal score is very good and it wins over Result<T,E>
for me now. Going to fill some radars to make Swift better. Do it as well, because dupes are counted as
votes and they’re listening. I received response for all my bug reports (Swift related) very quickly,
which is unbelievable when I compare it to bug reports for another products.