Converting between Result, Optionals, and Swift 2 errors
June 15, 2015
Hey! This is an old post. It reflects my views and the state of the world in 2015, but is likely to be outdated. You’ll find me
on Twitter, building
Nozbe, and making
engineering blueprints.
Like interactive articles? Download Playground for this article.
Before Swift 2, there were three main ways of dealing with errors:
- Optionals: for the simplest cases when you don’t care about a reason why an operation failed.
- NSError: used by Cocoa frameworks; upon failure, a method sets an error object to the variable passed as a pointer. This method is undesirable because of its verbosity, and because it’s too easy to ignore errors.
- Result: an enumeration that’s either a
.Success(value)
or a .Failure(error)
; common pattern among Swift programmers.
(There are also Objective-C exceptions and runtime traps, but those are fatal errors.)
Swift 2 introduces a new error handling model where functions can throw an error, and callers can catch
them. This replaces NSError
in bridged Objective-C classes.
It will also largely replace Result
. The convenient, familiar, compact syntax of Swift 2 errors is simply better than Result
in most cases.
There is an argument to be made, however, that even in Swift 2, Result
still has a place. Since it’s a value, you can pass it as an argument (for example, to an async operation handler). You can put it in an array, deal with it lazily, or save in a property. And you can create functions to easily transform, compose and chain those values together.
Also, if you already have a large codebase using Result
, you don’t necessarily want to rewrite it all at once.
Whatever the case, if you do continue using Result
in your Swift 2 code, you might find these conversion helpers useful:
Throwable to Result
Simply try
a throwable function in the auto-closure to convert it to a Result
:
(Why a free function? The most intuitive place would be a Result
initializer, but because throwable functions have untyped errors, we need to specialize Result
’s error type as ErrorType
, which isn’t possible with initializers. The second choice would be a static func
, but it crashes the compiler in the current beta.)
Result to throwable
Now you can try
calling .extract()
on a Result
:
The above implementation is simplistic — it will crash at runtime if result’s error type isn’t an ErrorType
. It would be best to add a <E: ErrorType>
constraint to Result
, but in the current beta, it crashes the compiler.
I also wrote a safe implementation using protocol extensions — you’ll find it in the Playground.
Optional to throwable
It’s not uncommon to see a function that’s a “pipeline” of multiple operations, each of which can return nil
. It might make sense to catch those nils at each stage of the pipeline and throw distinct errors for each of them. You could do that with if let
, or guard
, but here’s another idea:
And now you can unwrap values from optionals similarly to the nil coalescing operator (??
) — but instead of replacing nils with non-nil values, it throws errors. Here’s an example:
Now, should you actually create a custom operator like that, and give it a potentially confusing name like that? Perhaps not, it’s up to you. But you have to admit, it is pretty neat!
(Rob Napier has some more exploration of this in his Gist)
Conclusion
Who knows, maybe only hard-core FP enthusiasts will end up using Result
values. Or maybe a future Swift 2 beta will make these helpers obsolete. And the ?!
operator, we might conclude, is not worth the cost over a simple guard
statement.
But for now, it makes a lot of sense to experiment with those different error handling models and see how we can bridge them together.
Here’s a Playground with all these helpers and more exploration:
⬇︎ Download playground
Published June 15, 2015.
Send feedback.