Specifying custom error type within the Result type in Swift 5

Apologies for giving a second answer, but there needs to be a corrective for fphilipe's answer.

You can use init(catching:) to form the Result and yet return it as a Result<Model, CustomError>. That is what mapError is for! Like this:

enum CustomError: String, Error {
    case somethingBadHappened
}

struct Model {
    let value: Int
}

class Request {
    func execute(number: Int, completion: @escaping (Result<Model, CustomError>) -> Void) {
        let result = Result { () throws -> Model in
            if (number < 20) {
                throw NSError()
            } else {
                return Model(value: number)
            }
        }.mapError { err in
            return CustomError.somethingBadHappened
        }
        completion(result)
    }
}

I have to throw something in order to form the initial Result<Model, Error>, so I just throw an NSError as a kind of placeholder. But then mapError comes along and transforms this into a Result<Model, CustomError>. The power of mapError is that it changes only what happens in case of failure.

Thus we are able to keep the original form of the code.


Changing the signature of the completion closure to completion: @escaping (Result<Model, Error>) -> Void works, but then I am not using the custom error type.

Yes, you are! Change the signature in exactly that way, so that you compile, and then run your code. When we get to this line:

case .failure(let error): print("Failed with \(error)")

... we print "Failed with somethingBadHappened". That proves that your CustomError.somethingBadHappened instance came through just fine.

If the problem is that you want to separate out your CustomError explicitly, then separate it out explicitly as you catch it:

case .failure(let error as CustomError): print(error)
default : fatalError("oops, got some other error")

Or if you want to winnow it down still further and catch only the .somethingBadHappened case, specify that:

case .failure(CustomError.somethingBadHappened): print("Something bad happened")
default : fatalError("oops, got some other error")

Those examples are artificial, but they demonstrate what they are intended to demonstrate — that your CustomError instance is coming through with full integrity.


Just create Result manually:

let result: Result<Model, CustomError>
if (number < 20) {
    result = .failure(.somethingBadHappened)
} else {
    result = .success(Model(value: number))
}
completion(result)

Tags:

Swift