Friday, June 19, 2015

Swift 2 Error Handling, Continued

Mikael Konradsson:

Finally a [decent] exception model in a modern language, a model where it’s absolutely obvious when to use do/try/catch. If you miss it, the compiler (precompiler/static analyzer) will tell you. There is no, or very little overhead in using Exceptions in Swift, unlike Java and C# where the runtime must create the exception and unwind the stack and then throw.

Clark Goble:

My own view is that people are perhaps taking things too seriously. Swift is a very pragmatic language and it’s developing with a set of practical concerns. Mainly the connection to ObjC based Cocoa libraries. Maybe that’s undermining what the language could be. But especially for the next years, the primary purpose of Swift will be to code against Cocoa. For all the problems and limits of Swift exceptions, they do seem a big step up from difficult to read nested if statements. Maybe this decision will limit Swift in the future. But in the present it’ll make most people’s life easier.

Paul Hudson:

This does have one downside: if you add any future values to the enum, which we’re about to do, it will just drop into the default catch block – you won’t be asked to provide any code for it as would happen with a switch/case block.

David Owens II:

The latest thing I’m running up against in the cumbersomeness of the do-catch pattern for handling errors. Not only that, I think it promotes the bad exception handling that Java had with people simply writing a single catch (Exception e) handler.

Paul Hudson:

Swift 2 introduces the defer keyword, which effectively means “here’s some what I want you to do later, no matter what.” That work can be whatever you want: a single method call closing a file, or 50 lines of code doing some other important clean up work. The important thing is that Swift ensures that it will be run before the current scope is ended.

[…]

One of the most powerful features of defer is that you can stack up multiple deferred pieces of work, and Swift will ensure they all get executed. What’s more, it executes them in reverse order, meaning that the most recently deferred thing gets run first – effectively unwinding a stack.

Nick Lockwood on Brad Larson’s post:

If you look at the examples in that article, the reason why the result enum doesn’t work well for him is that the methods being called are primarily synchronous, and without return values. They aren’t pure functions. […] But does that mean result types are bad? No, they’re pretty awesome for functions that actually return a result that we want to pass to the next function (possibly an unspecified amount of time later). They just aren’t great for handling errors produced by procedures that don’t return anything, and execute sequentially on the same thread, because they must be checked after every call, and that quickly becomes tedious.

[…]

But try only works in this one situation, with sequential imperative statements. If Brad Larson was working on something like a network library instead of a robot controller, result types would work much better for error propagation than Swift 2’s exceptions, because exception-style errors don’t really work for asynchronous, callback-based APIs.

[…]

It’s gross that we have two totally different ways to handle errors based on whether the following line needs to use the output from the previous line, and whether it’s called immediately, or after an async calculation. And Swift’s try/catch semantics do nothing to help reduce the error handling boilerplate needed inside callbacks.

Rob Napier:

And “throws” is probably best thought of as somewhat opaque sugar around an Either type.

[…]

In fact, (Int) -> String is a subtype of (Int) throws -> String, which is pretty awesome and a little subtle, but we’ll get to that in another post.

Rob Napier:

So that tells us that a non-throwing closure can be used anywhere a throwing closure is requested, just like a Cat can be used anywhere an Animal is requested. No conversions necessary. It’s just types.

[…]

Luckily, Swift is much smarter than that. It’s nice that you can overload based on throwing, but in many cases we have a better tool. We can mark the method rethrows rather than throws.

func map<T>(@noescape transform: (Generator.Element) throws -> T) rethrows -> [T]

So what promise does rethrows make? It promises that the only way it will throw is if a closure it is passed throws. So if it’s passed a closure that can’t throw, the compiler knows that the function can’t throw either.

Anthony Levings:

Internally the rethrowing function uses the try keyword as usual, but does not require the do and catch elements. It is only when the function is called that the do-try-catch syntax is employed.

Erica Sadun:

If you’re working with asynchronous closures, you’ll need to supply ones that do not use throwing signatures. Convert your closures to non-throwing versions by providing exhaustive catch clauses.

Update (2015-06-22): Joshua Emmons:

It’s telling that exceptions have arrived in the language alongside multi-payload enums. This makes it trivial to create Either types without the awkward, inefficient boxing of values previously required. And it would seem exceptions take advantage of this. throws is just syntactic sugar.

Update (2015-06-25): Juan Pablo Claude:

Swift 2.0 does a great job of coalescing the history of error handling in Cocoa and Cocoa Touch into a modern idiom that will feel familiar to many programmers. Unifying behavior leaves the Swift language and the frameworks it inherits in a good position to evolve.

6 Comments RSS · Twitter

[…] Notes from WWDC 2015: Failing Gracefully: Swift 2.0 Error Handling; Swift 2 Error Handling, Continued […]

What's odd is it is possible to unify async and sync handling to use throws, but the bridging doesn't do it. The trick is to pass the completion a throws closure. I'm surely not the first nor the last to arrive at this conclusion, but here's that sketch with an actual example: https://jeremywsherman.com/blog/2015/06/17/using-swift-throws-with-completion-callbacks/

ErrorType doesn't address the main problem with handling Cocoa errors: the lack of documentation of what methods might trigger what errors. Faced with a bunch of methods glossed as "it returns some error", aside from experimentation to try to trigger some errors you might handle gracefully, there's little else you can do beyond log it or show it to the user with presentError: - and UIKit doesn't even offer the latter option.

@Jeremy: Yeah, knowing the types of error values a function can return are just as important as knowing the types of non-error return values.

Lack of a meaningful error class hierarchy has always been a Cocoa shortcoming, but hopefully Apple will now have some motivation to address this. At which point I imagine specific error types could be declared after the `throws`, and `do-catch` blocks extended to have multiple `catch` blocks dispatching by type. (Not that you can't already dispatch based on `NSError.code`, of course, but pushing such information out of runtime logic and into the type system provides benefits all round.)

[…] This should make error handling easier than before. […]

I have a post about using result type that I really need to write up about this.

[…] Notes from WWDC 2015: Failing Gracefully: Swift 2.0 Error Handling; Swift 2 Error Handling, Continued […]

Leave a Comment