The nice thing about try?
is that you don’t have to encapsulate calls within a do-catch
block. Your result returns as an optional: .Some
for success, .None
for fail. You can use try?
in both if-let
statements, and with guard
.
The bad thing about try?
is that it discards errors, leaving you unable to figure out what when wrong when things go wrong. That’s not really a great thing.
Instead, you can roll your own alternative. Implement a simple result enumeration:
enum Result<T> { case Value(T) case Error(ErrorType) }
(As you can see, I’m not a big fan of err
and ok
, but use the enumeration cases you prefer.) Then build a function that performs the do-catch and wraps the results for you in the enumeration.
func tryit<T>(block: () throws -> T) -> Result<T> { do { let value = try block() return Result.Value(value) } catch {return Result.Error(error)} }
(I don’t like the name tryit
and am completely open to alternate names.) The resulting call is a little wordier. Instead of
let result = try myFailableCoinToss()
you call
let result = tryit{try myFailableCoinToss()} Thanks, bigonotetaker
let result = tryit(myFailableCoinToss)
Reader glessard has a terrific alternative as well, suggesting adding an initializer for Result instead of using tryit:
enum Result<T> { case Value(T) case Error(ErrorType) init(_ block: () throws -> T) { do { let value = try block() self = Result.Value(value) } catch { self = Result.Error(error) } } }
so you just call:
let result = Result(myFailableCoinToss)
You need to unwrap your result outside of if-let
and guard
. You can use a switch:
let result = tryit{try myFailableCoinToss()}
let result = tryit(myFailableCoinToss)
switch result {
case .Value(let value): print("Success:", value)
case .Error(let error): print("Failure:", error)
}
Or pattern matching:
if case .Value(let value) = result { print("Success:", value) } else if case .Error(let error) = result { print("Failure:", error) }
You can add some scope-leaving behavior to mimic guard. It’s doable but ugly.
enum Result<T> {
case Value(T)
case Error(ErrorType)
func unwrap() throws -> T {
if case .Value(let value) = self {return value}
throw "Unable to unwrap result"
}
func handleError(errorHandler: ErrorType -> Void) -> Bool {
if case .Error(let error) = self {
errorHandler(error)
return true
}
return false
}
}
func tryit<T>(block: () throws -> T) -> Result<T> {
do {
let value = try block()
return Result.Value(value)
} catch {return Result.Error(error)}
}
let result = tryit{try myFailableCoinToss()}
let result = tryit(myFailableCoinToss)
// guard error
if result.handleError({
error in
print("Error is \(error)")
}) {fatalError()} // leave scope on true
// force try for success case
let unwrappedResult = try! result.unwrap()
// result is now usable at top level scope
print("Result is \(unwrappedResult)")
Here’s another approach that more closely mimics try?
but at least prints any errors that are raised.
func tryit<T>(block: () throws -> T) -> Optional<T>{ do { return try block() } catch { print(error) return nil } }
This second approach earns you the if-let
and guard
behavior of try?
but ensures a measure of respect to returned errors. You call it like the results version:
let result = tryit{try myFailableCoinToss()}
let result = tryit(myFailableCoinToss)
You still won’t be able to base error recovery strategies on the error type and its details but it’s not completely throwing away that information either.
You can also adjust tryit
to accept an error-handling block, which leaves you with a bloated function that expects two closures. I messed around with this and a few other approaches but didn’t love any of them enough to share here. The biggest issue is that even if you pass an error handling block to try-it to , you can’t leave scope the way guard leaves scope and guard doesn’t offer an alternate form that passes a try?
error as a parameter to the scope-leaving block.
I suppose what I am approaching here is a construct that looks like the following, and that performs a conditional assignment at the top level scope and uses the scope-escaping block the way guard does in place of a normal try:
guard let result = try!! myFailableCoinToss() else {error in ...}
That’s because the current context either cares about the working with error or it does not. If it does not, you can just try. Boom you’re done.
let result = try myFailableCoinToss()
In the current state of things, if it does care (even if it then rethrows the same error after paying attention to it), you must use do-catch
or apply a workaround like the Result enumeration.
As always, big thanks to Mike Ash
5 Comments
How about resultOf instead of tryiit?
(also, you can omit the try and (): tryit(myFailableCoinToss))
Thanks! Updated the tryit calls. Pondering resultOf. 🙂
Or how about an initializer for Result to do the job of the tryit function?
Love that!
The second flavor of tryit — the logging equivalent to try? — seems really useful! When your code doesn’t care about the error, you still might want to see it for debugging purposes.
I’m having trouble seeing the advantage of the first form of tryit. Isn’t this:
let tossResult
let result = tryit(myFailableCoinToss)
switch result {
case .Value(let value):
tossResult = value
case .Error(let error):
handleError(error)
return
}
…merely a verbose equivalent to this?
let tossResult
do {
tossResult = try myFailableCoinToss()
} catch {
handleError(error)
}
I feel like I’m missing something.