Optionals, while not unique to Swift, are of course one of its most core features. But since Swift 5, it's also had a Result<T,E>
type that in many ways behaves similarly. Both are enums, often indicating a potentially failed/aborted result, but Result
gives you a bit more information about why something failed, whereas with Optional
you just have to guess.
One problem with Result
, in my opinion, is ergonomics. I often want to use result types in similar ways as optionals, and Swift has really nice expressive support for use cases around Optional
, but for the analagous Result
usages, it suddenly becomes a nightmare.
Let's say I want to get a value from a dictionary or default to 0 if the key doesn't exist:
let value = dict[key] ?? 0
Easy Peasy! But if I want to do something similar with a Result
? Here are my options:
let value: Int = (try? returns_result_type().get()) ?? 0 // Yucky; I will be dead in the grave before I use error throwing for control flow
// Or maybe...
var value: Int
switch(returns_result_type()) {
case .success(let x):
value = x
case .failure(let err):
value = 0
}
// Way too long considering the simple semantics, too hard to read, just terrible.
// Or maybeeee...
extension Result {
func getOr(_ fallback: @autoclosure () -> Success) -> Success {
switch self {
case .success(let value): return value
case .failure: return fallback()
}
}
}
let value = returns_result_type().getOr(0)
// ... Hooray???
This is a big problem in my opinion for two reasons:
- Using
Result
when it does appear is gunky - Developers are encouraged to use
Optional
overResult
even whenResult
would make more sense, becauseOptional
is so much more ergonomic.
About that second problem: Consider something like Int.init(_ text: String)
, which could fail if the text is non-numeric, or if it is numeric but would overflow (Granted, this is a constructor, but you see what I mean). This seems like the perfect use-case for result types, but it gives an Optional
instead. Without that extra information, if the function returns nil then I have to give my user a message like "The number you inputted was invalid" instead of a more specific "Please enter a shorter number." Or I implement the validation myself, but then what's the point anymore. If Result
was easier to use at the call site, I think developers would be less afraid to put it in libraries where functions have multiple distinct failure modes.
So what can we do about it?
I suggest extending the already-in-place tools surrounding optionals in Swift, like ??
, to work with Result
as well. This could be done through a few avenues. The simplest would be to just add a method like Result.toOptional()
which could be called whenever you don't care about distinct failure cases. Another option would be implementing some kind of shared interface across Optional
and Result
which operators like ??
could use. This interface could even be extended DIY, like if someone wanted to implement something like Haskell's Validation
to Swift. Lastly, Swift could just add more useful methods to Result
, but I think that lacks imagination.
What do you think? Am I the only one in Swift who still cares about Result
after async/await
?