Weekly Swift articles, podcasts and tips by John Sundell.

Result’s convenience APIs

Published on 24 Jan 2020

The Swift standard library’s Result type might just be a simple enum with cases for modeling an operation’s success or failure, but it also comes with several really useful convenience APIs built-in.

For example, it has a closure-based initializer, which we can use to easily convert any expression that throws into a Result value:

let data: Data = ...
let decoder = JSONDecoder()

// This will create a Result<Model, Error> instance, which will contain
// 'success' if its expression succeeded, and 'failure' if it ended
// up throwing an error:
let result = Result {
    try decoder.decode(Model.self, from: data)
}

// The above API is really useful when we want to return a Result
// value from an asynchronous operation, for example by calling
// a completion handler:
completionHandler(result)

Above we’re converting a raw Data value into a decoded Result, but we can also do the opposite — and extract a Data value from a Result instance, which we’ll then decode:

func decode(_ result: Result<Data, Error>) throws -> Model {
    // Using Result's 'get' method, we can either extract its
    // underlying value, or throw any error that it contains:
    let data = try result.get()
    let decoder = JSONDecoder()
    return try decoder.decode(Model.self, from: data)
}

Finally, Result also ships with a series of really useful mapping functions, for example map — which enables us to convert any result’s value into a new type, without having to do any error handling as part of such an operation:

let dataResult: Result<Data, Error> = ...

let stringResult = dataResult.map {
    String(decoding: $0, as: UTF8.self)
}