Never-failing Result Type

Hey,

inspired by Combine.Publisher.replaceError(with:) I would like to add replaceFailure(with:) to the Result Type.

  • No dependency on Combine
  • Synchron

Implementation:

extension Result {
    func replaceFailure(with replacement: Success) -> Result<Success, Never> {
        switch self {
        case let .success(success):
            return .success(success)
        case .failure:
            return .success(replacement)
        }
    }
}

extension Result where Failure == Never {
    func get() -> Success {
        switch self {
        case let .success(success):
            return success
        }
    }
}

Example:

func getNextInteger() -> Result<Int, Error> { /* ... */ }
let replacementInt = 42
 
let integerResult = getNextInteger()
// integerResult == .failure(/* some Error */)
let nonFailingIntResult = integerResult.replaceFailure(with: replacementInt)
// nonFailingIntResult == .success(42)
let safeInt = nonFailingIntResult.get()
2 Likes

Your example is equivalent to: (try? integerResult.get()) ?? 42, which reads quite well to me.

I think it can be useful when chaining calls together (like with Combine), otherwise a (try? <get>) ?? <default_value> works as well.

We call it recover in PromiseKit.

Yes, this simple sample is equivalent to your answer. But, like @suyashsrijan already said, my purpose was chaining results (like Combine) and get, at the end, one success value. But Combine is Apples framework and it`s not open source. Personally I would love to see more usage of the result type and I think, this extension is a good step.

1 Like

That looks reasonable at first glance, but when I think about it for a bit I can’t see the use for a Result<T, Never>, since it’s equivalent to a plain T. This is quite different from reactive and futures frameworks, where a Publisher/Promise/Signal<T, Never> is a useful and common thing.

I'd definitely like to see a replaceFailure(ofType: with:) method that replaces errors of a given type with a default value. I could also see a version replaceFailure(_: with:) which replaces a specific error with the given success value.

I agree. This was my first idea too. But instead of:

enum SomeError: Error { case network, case invalidData }

let result: Result<Data, SomeError> = .failure(.network)

result.replaceFailure(.network, with: NetworkFallback) // type?
      .replaceFailure(.invalidData, with: DataFallback) // not possible

I would suggest:

func replaceFailure(_ transform: (Failure) -> Success) -> Result<Success, Never> {
    switch self {
    case let .success(success):
        return .success(success)
    case let .failure(failure):
        return .success(transform(failure))
    }
}

usage:

let result = Result<Data, SomeError>.failure(.network)
            .replaceFailure { error in 
                switch (error) {
                case .network: return NetworkFallback
                case .invalidData: return DataFallback
            }

Yes, this was the intention. The idea is safely get the/one value. Currently, you can get the same result by:

let result = Result<Data, SomeError>.failure(.network)
let data: Data

do {
    data = try result.get()
} catch {
    error // type is Error instead of SomeError
    data = genericFallback
}
print(data)

(try? result.get()) ?? genericFallback // shorthand

But using the do/catch variant, the error is a type of Error instead of SomeError. There is no easy/short way to replace the error with different "fallbacks", depending on the specific error of the Failure without handling the general error.

With try/catch:

let data: Data
do {
    data = try result.get()
} catch SomeError.network {
   data = networkFallback
} catch SomeError. invalidData {
   data = dataFallback
} catch {
   error // type is Error
   data = genericFallback
}