Reputation: 6967
I'm trying to wrap my head around generic type constraints in Swift. Here is my starting point:
class Promise<T> {
func resolve(_ block:@escaping (T) ->Void) {}
func fulfill(_ result:T) {}
}
A Promise is just that, something that can be fulfilled in the future. This becomes very useful when it is used with Swift's Result
type when handing results back from a background queue to the main queue:
let promise = Promise<Result<String, Error>>()
promise.fulfill(.success("Hello"))
promise.fulfill(.failure(NSError()))
Now I would like to add an extension to all Promise instances that use Result
to add these helper methods:
extension Promise where T == Result<X, Error> {
⬆︎ Here's the problem ⚡️
func failure(_ error:Error) {
fulfill(.failure(error))
}
func success(_ result:X) {
fulfill(.success(result))
}
}
// Shorter:
let promise = Promise<Result<String, Error>>()
promise.success("Hello")
promise.failure(NSError())
Only problem is that the above code does not compile, because X
is not defined. What I want to express is this:
Extend Promise
when its generic type T
is of type Result<X,Z>
where X
can be anything and Z
must be of type Error
→ Result<*, Error>
. Is this possible?
Upvotes: 6
Views: 1867
Reputation: 299355
What you want is possible, it's just that the syntax is slightly verbose. You can't put the where
constraint on the extension. You have to put it on each method.
extension Promise {
func failure<U>(_ error: Error) where T == Result<U, Error> {
fulfill(.failure(error))
}
func success<U>(_ result: U) where T == Result<U, Error> {
fulfill(.success(result))
}
}
You can also do it with a protocol, but for enums I find this very klunky, because enum cases can't be treated as conforming methods.
protocol ResultType {
associatedtype Success
associatedtype Failure: Error
static func makeFailure(_: Failure) -> Self
static func makeSuccess(_: Success) -> Self
}
extension Result: ResultType {
static func makeFailure(_ failure: Failure) -> Result<Success, Failure> { .failure(failure) }
static func makeSuccess(_ success: Success) -> Result<Success, Failure> { .success(success) }
}
extension Promise where T: ResultType {
func failure(_ error: T.Failure) {
fulfill(T.makeFailure(error))
}
func success(_ result: T.Success) {
fulfill(T.makeSuccess(result))
}
}
Upvotes: 7