radex.io

aboutarchiveetsybooksmastodontwitter

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:

(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

func catchResult<T>(@autoclosure f: () throws -> T) -> Result<T, ErrorType> {
    do {
        return .Success(try f())
    } catch {
        return .Failure(error)
    }
}

Simply try a throwable function in the auto-closure to convert it to a Result:

catchResult(try NSData(contentsOfFile: "data.bin", options: []))

(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

extension Result {
    func extract() throws -> T {
        switch self {
        case .Success(let value):
            return value
        case .Failure(let error):
            throw error as! ErrorType
        }
    }
}

Now you can try calling .extract() on a Result:

do {
    let json = try jsonResult.extract()
} catch {
    print(error)
}

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:

infix operator ?! {
    associativity right
    precedence 131
}

func ?! <T>(optional: T?, @autoclosure error: () -> ErrorType) throws -> T {
    if let value = optional {
        return value
    } else {
        throw error()
    }
}

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:

func parseJSON(data: AnyObject?) throws -> Task {
    let json     = try data as? NSDictionary ?! MyError.NoJSON
    let taskData = try extractTaskData(json) ?! MyError.NoTaskData
    return         try Task(json: taskData)  ?! MyError.ParsingError
}

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.