In my app, I have a function to call a specific API endpoint of mine, and that function accepts a closure as a completion handler. That closure accepts a Result
of my custom Decodable
type (Category
) in the success
case, and an Error
in the failure
case. Altogether, its method signature looks like this:
static func getAllRestaurantCategories(completion: @escaping (Result<[Category], Error>) -> Void) -> Void
This function calls out to Alamofire to determine the languages that the server supports, and then get the list of all possible restaurant categories. It's implemented like this:
static func getAllRestaurantCategories(completion: @escaping (Result<[Category], Error>) -> Void) -> Void{
API.localizedRequest(API.categories) { (request: DataRequest) in
request.responseDecodable(of: [Category].self) { (response: DataResponse<[Category], AFError>) in
completion(response.result)
}
}
}
However, on the line with completion(response.result)
, I get a compiler error that says Cannot convert value of type 'Result<[Category], AFError>' to expected argument type 'Result<[Category], Error>'
. This error goes away if I change the closure my method accepts to accept an AFError
in the failure
case, like this:
static func getAllRestaurantCategories(completion: @escaping (Result<[Category], AFError>) -> Void) -> Void{
API.localizedRequest(API.categories) { (request: DataRequest) in
request.responseDecodable(of: [Category].self) { (response: DataResponse<[Category], AFError>) in
completion(response.result)
}
}
}
Alamofire's AFError
conforms to Error
, so it seems to me that this should work just fine. I know I can parse Alamofire's Result
myself and generate my own to pass to my completion handler, but I'd rather not write all that extra custom code if I don't have to. How can I get the type system to understand that this should be ok?
Simply put, in (at least the current version of) Swift, if Sub
is a subtype of Base
, that doesn't mean that Container<Sub>
is a subtype of Container<Base>
.
In fact, Container<Sub>
and Container<Base>
are unrelated types.
So, while we can do the following:
protocol Car {}
struct Toyota: Car {}
let a: Car = Toyota()
but we can't generally (with notable exception of Swift's standard library collection types) do this:
struct Container<T> {}
let c: Container<Car> = Container<Toyota>() // ERROR
It is said that Container<Car>
and Container<Toyota>
are not covariant
Result
has a mapError
function that should make it fairly painless:
completion(response.result.mapError { $0 as Error } )