With Swift 2.0, you’ve probably been doing something like this to handle errors.
let foo = try somethingThatMaythrow
This calls a routine that potentially throws an error.
Somewhere in that calling chain, someone takes responsibility for catching errors, typically with do-catch
. In this paradigm, the error is of interest primarily at the point of creation and the point of consumption. No other code needs deal with error conditions. Try
is sufficient to pass the error handling along.
If you want to ignore the error chain, an exclamation point signals “success or die”. Any fail scenario creates a run-time crash.
let foo = try! somethingThatMayThrowIfItFailsCrashyCrashy
Now Beta 6 introduces try?
(with a question mark). It returns an optional that wraps successful values and “catches” errors by returning nil.
guard let foo = try? somethingThatMayThrow else { handle error condition and leave scope}
if let foo = try? somethingThatMayThrow {}
In each of these example statements, the code conditionally binds a non-optional to foo, but you can assign foo directly and it’s assigned as an optional.
You can combine try? with ?? for fallback values but you must surround the try in parens at this point, as it took me several minutes to figure out:
In both uses, be aware that your error is discarded. Instead of:
if (!(value = [instance request:blah withError:&error])) { ...print error and return/die... }
or:
if let value = instance.request(blah, error:&error) { ...success.. } else { ...print error and return/die... }
or:
do { let value = try instance.request(blah) } catch {... print error and return/die ...}
You’re working in a system where error conditions are transformed automatically into nil values.
So, what this gets you:
- Interoperability between Swift’s nullable optionals and its error system. For example, you can write throwing functions and consume them with if-let.
- A focus on success/failure where the calling context solely assumes responsibility for reporting error conditions. “I tried to do X and it failed somewhere.” Traditionally errors describe what went wrong. Now you describe what you were trying to do.
What you lose:
- Cocoa-style error handling. Any errors that are generated are eaten away and you won’t see them or know about them.
- Error source information such as the file / routine that generated the issue. All you know is that the calling chain failed somewhere but not why or how.
- Railway/Freight Train-style development, or as Jeremy Tregunna puts it, you lose “the either monad as inputs and outputs to code that can fail with left side being the error, right side being the value; binding these functions together with a binding operator that automatically propogates the first error through to the end of the chain”
When do you want to do this?
- When you’re focused more on success/failure than why things failed.
- When you’re working with well-tested API chains, so if you can’t construct a URL or save to file or whatever, you just want to note it and move on with your recovery code for a “did not succeed scenario”
This construct more or less steps back to using optional return values that signal success and failure, which we had moved beyond earlier this Summer. It’s time to rewrite a few manifestos.
In summary: ahoy ahoy, it’s a major paradigm shift. Developers take note.
5 Comments
Hey Erica.
I am really glad about the try? keyword.
It makes a lot of things easier.
I was writing so much boilerplate for error handling that I wondered, if I coud write a „catching to Optional“ function on my own, so I came up with this:
func catched(f: () throws -> T) -> T? {
do {
return try f()
}
catch {
return nil
}
}
Unfortunately @autoclosures couldn’t throw, so I wasn’t able to turn this in an operator.
But with beta 6 this is now possible (but not necessary anymore)
As I read your post this morning, I realized, that swift error handling, monad style error handling with a Result-Type and error handling with optionals are not necessarily mutually exclusive,
in fact, with the current changes, they play together pretty well.
Also it’s easy to convert the „unofficial“ error handing with Optionals into a throwing function with a simple operator – swift has all the tools.
I’ve put together some functions/operators which are possibly trivial, but maybe not obvious for everyone:
https://gist.github.com/953c80ec1f9b96008539.git
What do you think?
Sorry, wrong gist-URL, this should work:
https://gist.github.com/heiko-henrich/953c80ec1f9b96008539
And yet, ObjC has all this and more already, and I can’t help but wonder why we’re not playing with an ObjC 3.x instead. Oh well.
Fundamental improvements in a language design can’t show up in point releases. Swift’s Optional type, and the guarantee that only Optionals can contain nil is one such fundamental thing. I think it’s in the nature of creating a new language that those fundamental components that are unlikely to change during the lifetime of the language be established first and certain “quality of life” features get deferred.
I like it. Coming from a Scala / functional background, I have a strong “exceptions are bad” predisposition. Of course Swift is not purely functional, but I love these evolutions of the language – they make it easier to become more functional, less imperative.