Adding nil coalescing to result
15 Nov 2020It’s not rare to have code that uses Swift’s Result
in a simplified way.
Not all call sites need to handle the error and may choose to throw the error away, opting instead to provide a default value.
There are a many ways to achieve this goal - here’s three alternate spellings to achieve the same thing:
let deliveryStatus: String
switch lookupDeliveryStatus() {
case let .success(value):
deliveryStatus = value
case .failure:
deliveryStatus = "In progress..."
}
Or
let deliveryStatus: String
if case let .success(value) = lookupDeliveryStatus() {
deliveryStatus = value
} else {
deliveryStatus = "In progress..."
}
Or
let deliveryStatus = (try? lookupDeliveryStatus().get()) ?? "In progress..."
The last example is pretty short but it’s quite noisy with a try?
that is wrapped in ()
and then a usage of the nil-coalescing operator (??
).
We can reduce a lot of the noise by defining an implementation of the nil-coalescing operator on the Result
type directly.
extension Result {
public static func ?? (result: Result, defaultValue: @autoclosure () throws -> Success) rethrows -> Success {
switch result {
case .success(let value):
return value
case .failure:
return try defaultValue()
}
}
}
With this in place our original example becomes much more succinct
let deliveryStatus = lookupDeliveryStatus() ?? "In progress..."
The above felt a little uncomfortable to begin with because the thing returning a Result
obviously felt it was important enough to provide some additional context in the event of a failure so surely we should pay attention to it.
I think this tension is interesting as it causes a pause to consider whether you really should be handling the error or are happy to ignore it.
Something that eases the tension for me is that the actual implementation is a copy/paste/tweak job from the function defined to work on optionals, so there is prior art that it’s reasonable to provide a convenience for this operation.