Back in the early days of Swift 1, we didn’t have much in the way of error handling. But we did have Optional , and it felt awesome! By making null checks explicit and enforced, bombing out of a function by returning nil suddenly felt less like a code smell and more like a language feature.

Here’s how we might write a little utility to grab Keychain data returning nil for any errors:

func keychain Data ( service : String ) -> Data ? { let query : NSDictionary = [ k Sec Class : k Sec Class Generic Password , k Sec Attr Service : service , k Sec Return Data : true ] var ref : CFType Ref ? = nil switch Sec Item Copy Matching ( query , & ref ) { case err Sec Success : return ref as? Data default : return nil } }

We set up a query, pass an an empty inout reference to Sec Item Copy Matching and then, depending on the status code we get back, either return the reference as data or nil if there was an error.

At the call site, we can tell if something has exploded by unwrapping the optional:

if let my Data = keychain Data ( service : "My Service" ) { do something with my Data... } else { fatal Error ( "Something went wrong with... something?" ) }

Getting Results

There’s a certain binary elegance to the above, but it conceals an achilles heel. At its heart, Optional is just an enum that holds either some wrapped value or nothing:

enum Optional < Wrapped > { case some ( Wrapped ) case none }

This works just fine for our utility when everything goes right — we just return our value. But most operations that involve I/O can go wrong ( Sec Item Copy Matching , in particular, can go wrong many, many ways), and Optional ties our hands when it comes to signaling something’s gone sideways. Our only option is to return nothing.

Meanwhile, at the call site, we’re wondering what the issue is and all we’ve got to work with is this empty .none . It’s difficult to write robust software when every non-optimal condition is essentially reduced to ¯\_(ツ)_/¯ . How could we improve this situation?

One way would be to add some language-level features that let functions throw errors in addition to returning values. And this is, in fact, exactly what Swift did in version 2 with its throws/throw and do/catch syntax.

But let’s stick with our Optional line of reasoning for just a moment. If the issue is that Optional can only hold a value or nil , and in the event of an error nil isn’t expressive enough, maybe we can address the issue simply by making a new Optional that holds either a value or an error?

Well, congratulations are in order: change a few names, and we see we just invented the new Result type, now available in the Swift 5 standard library!

enum Result < Success , Failure : Error > { case success ( Success ) case failure ( Failure ) }

Result holds either a successful value or an error. And we can use it to improve our little keychain utility.

First, let’s define a custom Error type with some more descriptive cases than a simple nil :

enum Keychain Error : Error { case not Data case not Found ( name : String ) case io Went Bad … }

Next we change our keychain Data definition to return Result<Data, Error> instead of Data? . When everything goes right we return our data as the associated value of a .success . What happens if any of Sec Item Copy Matching ’s many and varied disasters strike? Rather than returning nil we return one of our specific errors wrapped in a .failure :

func keychain Data ( service : String ) -> Result < Data , Error > { let query : NSDictionary = [ ... ] var ref : CFType Ref ? = nil switch Sec Item Copy Matching ( query , & ref ) { case err Sec Success : guard let data = ref as? Data else { return . failure ( Keychain Error . not Data ) } return . success ( data ) case err Sec Item Not Found : return . failure ( Keychain Error . not Found ( name : service )) case err Sec IO : return . failure ( Keychain Error . io Went Bad ) … } }

Now we have a lot more information to work with at the call site! We can, if we choose, switch over the result, handling both success and each error case individually:

switch keychain Data ( service : "My Service" ) { case . success ( let data ): do something with data... case . failure ( Keychain Error . not Found ( let name )): print ( " \( name ) not found in keychain." ) case . failure ( Keychain Error . io Went Bad ): print ( "Error reading from the keychain." ) case . failure ( Keychain Error . not Data ): print ( "Keychain is broken." ) … }

All things considered, Result seems like a pretty useful upgrade to Optional . How on earth did it take it five years to be added to the standard library?

Three’s a Crowd

Alas, Result is also cursed with an achilles heel — we just haven’t noticed it yet because, up until now, we’ve only been working with a single call to a single function. But imagine we add two more error-prone operations to our list of Result -returning utilities:

func make Avatar ( from user : Data ) -> Result < UIImage , Error > { Return avatar made from user's initials... or return failure... } func save ( image : UIImage ) -> Result < Void , Error > { Save image and return success... or returns failure... }

Note the return type of save(image:) — its success type is defined as Void . We don’t always have to return a value with our successes. Sometimes just knowing it succeeded is enough.

In our example, the first function generates an avatar from user data, and the second writes an image to disk. The implementations don’t matter so much for our purposes, just that they return a Result type.

Now, how would we write something that fetches user data from the keychain, uses it to create an avatar, saves that avatar to disk, and handles any errors that might occur along the way?

We might try something like:

switch keychain Data ( service : "User Data" ) { case . success ( let user Data ): switch make Avatar ( from : user Data ) { case . success ( let avatar ): switch save ( image : avatar ) { case . success : break // continue on with our program... case . failure ( File System Error . read Only ): print ( "Can't write to disk." ) … } case . failure ( Avatar Error . invalid User Format ): print ( "Unable to generate avatar from given user." ) … } case . failure ( Keychain Error . not Found ( let name )): print ( "" \( name ) " not found in keychain." ) … }

But whooo boy. Adding just two functions has led to an explosion of nesting, dislocated error handling, and woe.

Falling Flat

Thankfully, we can clean this up by taking advantage of the fact that, like Optional , Result implements flat Map . Specifically, flat Map on a Result will, in the case of .success , apply the given transform to the associated value and return the newly produced Result . In the case of a .failure , however, flat Map simply passes the .failure and its associated error along without modification.

Things that implement flat Map like this are sometimes called “monads”. This can be a useful sobriquet to know as monads all share some common properties. But if the term is unfamiliar or scary, don’t sweat it. flat Map is the only thing we need to understand, here.

There’s a common misconception that flat Map has been replaced by compact Map as of Swift 4.1. Not so! Only the specific case of calling flat Map on a Sequence with Optional elements is deprecated.

Because it passes errors through in this manner, we can use flat Map to combine our operations together without checking for .failure each step of the way. This lets us minimize nesting and keep our error handling and operations distinct:

let result = keychain Data ( service : "User Data" ) . flat Map ( make Avatar ) . flat Map ( save ) switch result { case . success : break // continue on with our program... case . failure ( Keychain Error . not Found ( let name )): print ( "" \( name ) " not found in keychain." ) case . failure ( Avatar Error . invalid User Format ): print ( "Unable to generate avatar from given user." ) case . failure ( File System Error . read Only ): print ( "Can't write to disk." ) … }

This is, without a doubt, an improvement. But it requires us (and anyone reading our code) to be familiar enough with .flat Map to follow its somewhat unintuitive semantics.

And this is a best case scenario of perfect composability (the resulting value of the first operation being the required parameter of the next, and so on). What if an operation takes no parameters? Or requires more than one? Or takes a parameter of a different type than we’re returning? flat Map ing across those sorts of beasts is… less elegant.

Compare this to the do/catch syntax from all the way back in Swift 2 that we alluded to a little earlier:

do { let user Data = try keychain Data ( service : "User Data" ) let avatar = try make Avatar ( from : user Data ) try save ( image : avatar ) } catch Keychain Error . not Found ( let name ) { print ( "" \( name ) " not found in keychain." ) } catch Avatar Error . invalid User Format { print ( "Not enough memory to create avatar." ) } catch File System Error . read Only { print ( "Could not save avatar to read-only media." ) } …

The first thing that might stand out is how similar these two pieces of code are. They both have a section up top for executing our operations. And both have a section down below for matching errors and handling them.

This similarity is not accidental. Much of Swift’s error handling is sugar around returning and unwrapping Result -like types. As we’ll see more of in a bit…

Whereas the Result version has us piping operations through chained calls to flat Map , we write the do/catch code more or less exactly as we would if no error handling were involved. While the Result version requires we understand the internals of its enumeration and explicitly switch over it to match errors, the do/catch version lets us focus on the part we actually care about: the errors themselves.

By having language-level syntax for error handling, Swift effectively masks all the Result -related complexities it took us the first half of this post to digest: enumerations, associated values, generics, flatMap, monads… In some ways, Swift added error-handling syntax back in version 2 specifically so we wouldn’t have to deal with Result and its eccentricities.

Yet here we are, five years later, learning all about it. Why add it now?

Error’s Ups and Downs

Well, as it should happen, do/catch has this little thing we might call an achilles heel…

See, throw , like return , only works in one direction; up. We can throw an error “up” to the caller, but we can’t throw an error “down” as a parameter to another function we call.

This “up”-only behavior is typically what we want. Our keychain utility, rewritten once again with error handling, is all return s and throw s because its only job is passing either our data or an error back up to the thing that called it:

func keychain Data ( service : String ) throws -> Data { let query : NSDictionary = [ ... ] var ref : CFType Ref ? = nil switch Sec Item Copy Matching ( query , & ref ) { case err Sec Success : guard let data = ref as? Data else { throw Keychain Error . not Data } return data case err Sec Item Not Found : throw Keychain Error . not Found ( name : service ) case err Sec IO : throw Keychain Error . io Went Bad … } }

But what if, instead of fetching user data from the keychain, we want to get it from a cloud service? Even on a fast, reliable connection, loading data over a network can take a long time compared to reading it from disk. We don’t want to block the rest of our application while we wait, of course, so we’ll make it asynchronous.

But that means we’re no longer returning anything “up”. Instead we’re calling “down” into a closure on completion:

func user Data ( for user ID : String , completion : ( Data ) -> Void ) { get data from the network // Then, sometime later: completion ( my Data ) }

Now network operations can fail with all sorts of different errors, but we can’t throw them “down” into completion . So the next best option is to pass any errors along as a second (optional) parameter:

func user Data ( for user ID : String , completion : ( Data ?, Error ?) -> Void ) { Fetch data over the network... guard my Error == nil else { completion ( nil , my Error ) } completion ( my Data , nil ) }

But now the caller, in an effort to make sense of this cartesian maze of possible parameters, has to account for many impossible scenarios in addition to the ones we actually care about:

user Data ( for : "jemmons" ) { maybe Data , maybe Error in switch ( maybe Data , maybe Error ) { case let ( data ? , nil ): do something with data... case ( nil , URLError . timed Out ?): print ( "Connection timed out." ) case ( nil , nil ): fatal Error ( "🤔Hmm. This should never happen." ) case ( _ ?, _ ?): fatal Error ( "😱What would this even mean?" ) … } }

It’d be really helpful if, instead of this mishmash of “data or nil and error or nil” we had some succinct way to express simply “data or error”.

Stop Me If You’ve Heard This One…

Wait, data or error? That sounds familiar. What if we used a Result ?

func user Data ( for user ID : String , completion : ( Result < Data , Error > ) -> Void ) { // Everything went well: completion ( . success ( my Data )) // Something went wrong: completion ( . failure ( my Error )) }

And at the call site:

user Data ( for : "jemmons" ) { result in switch ( result ) { case ( . success ( let data )): do something with data... case ( . failure ( URLError . timed Out )): print ( "Connection timed out." ) … }

Ah ha! So we see that the Result type can serve as a concrete reification of Swift’s abstract idea of “that thing that’s returned when a function is marked as throws .” And as such, we can use it to deal with asynchronous operations that require concrete types for parameters passed to their completion handlers.

This duality between the abstract “error handling thing” and concrete “ Result thing” is more than just skin deep — they’re two sides of the same coin, as illustrated by how trivial it is to convert between them: Result { try something That Throws () } …turns an abstract catchable thing into a concrete result type that can be passed around. try some Result . get () …turns a concrete result into an abstract thing capable of being caught.

So, while the shape of Result has been implied by error handling since Swift 2 (and, indeed, quite a few developers have created their own versions of it in the intervening years), it’s now officially added to the standard library in Swift 5 — primarily as a way to deal with asynchronous errors.

Which is undoubtedly better than passing the double-optional (Value?, Error?) mess we saw earlier. But didn’t we just get finished making the case that Result tended to be overly verbose, nesty, and complex when dealing with more than one error-capable call? Yes we did.

And, in fact, this is even more of an issue in the async space since flat Map expects its transform to return synchronously. So we can’t use it to compose asynchronous operations:

user Data ( for : "jemmons" ) { user Result in switch user Result { case . success ( let user ): fetch Avatar ( for : user ) { avatar Result in switch avatar Result { case . success ( let avatar ): cloud Save ( image : avatar ) { save Result in switch save Result { case . success : // All done! case . failure ( URLError . timed Out ) print ( "Operation timed out." ) … } } case . failure ( Avatar Error . invalid User Format ): print ( "User not recognized." ) … } } case . failure ( URLError . not Connected To Internet ): print ( "No internet detected." ) … }

There is, actually, a flat Map -like way of handling this called the Continuation Monad. It’s complicated enough, though, that it probably warrants a few blog posts all unto itself.

Awaiting the Future

In the near term, we just have to lump it. It’s better than the other alternatives native to the language, and chaining asynchronous calls isn’t as common as for synchronous calls.

But in the future, just as Swift used do/catch syntax to define away Result nesting problems in synchronous error handling, there are many proposals being considered to do the same for asynchronous errors (and asynchronous processing, generally).

The async/await proposal is one such animal. If adopted it would reduce the above to:

do { let user = try await user Data ( for : "jemmons" ) let avatar = try await fetch Avatar ( for : user ) try await cloud Save ( image : avatar ) } catch Avatar Error . invalid User Format { print ( "User not recognized." ) } catch URLError . timed Out { print ( "Operation timed out." ) } catch URLError . not Connected To Internet { print ( "No internet detected." ) } …

Which, holy moley! As much as I love Result , I, for one, cannot wait for it to be made completely irrelevant by our glorious async/await overlords.