Friday, June 12, 2015

Swift 2 Error Handling in Practice

David Owens II:

It took me a little while for me to really appreciate the design of throws. At first, it was sheer disbelief that the Swift team created exceptions. Then it was mild irritation that a Result<T, E> type wasn’t used.

Brad Larson:

We aren’t the only ones that think Result, or a type like it, is a solid solution for error handling in Swift, so it took me by surprise that the Swift team went in a seemingly different direction with Swift 2’s new error model. That is, until I started working with it in my own code and found that it did almost everything my Result-based handling does and did it in a cleaner manner.

Swift 2 provides error handling by the means of several new keywords: do, try, catch, throw, throws. To indicate that a function or method can provide an error, you mark it with throw. Errors cannot be thrown within functions or methods that lack this marking. Similarly, error-throwing functions or methods cannot be used without using the try keyword before them. All errors must either be caught (using do/catch), or passed upward (at which point the function or method must be marked as throws).

Multiple error-throwing functions can be called in sequence, but the first to throw an error aborts execution at that point. An error being thrown acts more like a return statement than the older-style Objective-C NSExceptions, and plays nice with ARC.

My initial reaction was that Swift 2’s error handling is a bit of a hack, a lot of keywords and compiler magic to not give us real exceptions. But Swift fixes some of the problems with exceptions in other languages. And it fixes the main thing that was wrong with the Objective-C NSError model, which is that it was very verbose. Most importantly, it doesn’t turn ordinary code into a mess of types and closures, which is what I feared Apple was going to do.

I look at what it does to Larson’s code, and I’m impressed. And he hasn’t even showed what defer can do. So, although it’s unusual, I think this is going to work out well in practice.

There’s still work to be done, though. Swift still can’t handle actual exceptions raised by Objective-C code. They’re treated as runtime errors that bring down your whole program. So some important Cocoa APIs—plus anything with assertions—is off-limits.

See also: the two Swift books and my WWDC 2015 Links post.

Update (2015-06-12): I am going to miss the automatic stack traces that I would get with either true exceptions or my Objective-C NSError propagation macros.

4 Comments RSS · Twitter

I'm happy to be vindicated. I was one of the ones that complained on the Dev forums about the lack of exceptions in Swift. It took a while, but one particular Apple engineer worked hard to explain why the runtime exceptions were bad. Well, they weren't bad, Cocoa just never supported them. And silly me, I learned how to use them, read all the docs, appreciated all the upgrades, but then never noticed the one-line edit in 2010 that Apple slipped into the Objective-C Exception handling docs - "By the way - don't ever use any of this".

All the time, Apple was preaching the glory of the NSError and the dev flocks were singing their praise. I still didn't like it, but I couldn't do anything about it. But it turns out that Apple was saying one thing and coding the exact opposite the whole time.

Swift errors are not exceptions, they act just like NSError handling with enforced handling rules. You cannot catch ObjC exceptions (or C++) and you cannot catch errors that are forced to be unhandled.

enum MyError : ErrorType { case Error }

func f() throws {
throw MyError.Error
}

func g() {
try! f()
}

do {
g()
}
catch {
// you cannot catch the error that will be thrown because they really are not exceptions
}

MJ: "My initial reaction was that Swift 2’s error handling is a bit of a hack"

I do think you're right. The Swift devs seem to be doing an excellent job at poorly reinventing half of Common Lisp.

[…] Nick Lockwood on Brad Larson’s post: […]

Leave a Comment