rockbruno
(Bruno Rocha)
1
Consider the following code:
protocol Foo: AnyObject {}
func doSomething<T>(factory: @escaping () -> (T & Foo)) {
// ...
}
This fails to compile because the protocol composition T & Foo only works if T is either a class or another protocol. (Non-protocol, non-class type 'T' cannot be used within a protocol-constrained type)
However, this fails even when T is guaranteed to be a class:
func doSomething<T: AnyObject>(factory: @escaping () -> (T & Foo)) {
// ...
}
Is this a limitation of the compiler or have I misunderstood what T: AnyObject would actually mean in this context?
3 Likes
tera
2
Why not simply:
func doSomething<T: Foo>(factory: @escaping () -> T) { ... }
rockbruno
(Bruno Rocha)
3
The question is not about how to make the snippet compile, the question is why it fails to compile. The snippet is just an example.
tera
4
Got you. A simpler example:
protocol P {}
func foo<T: AnyObject>(_ v: T & P) {} // 🛑
// Non-protocol, non-class type 'T' cannot be used within a protocol-constrained type
Looks like a compiler limitation that it can't figure out that T is a class type.
trochoid
(Trochoid)
5
I’m not an expert so take this with a grain of salt but it looks like a generic type is not considered to be a protocol or class type so that’s why it can’t be used in a protocol-constrained type.
But you can make the generic type with the protocol constraints…
protocol P {}
func foo<T: AnyObject & P>(_ v: T) {} //✅
Joe_Groff
(Joe Groff)
6
Swift doesn't support generic subclass constraints. You can't write class Foo<T>: T, P or func foo<T: AnyObject, SubT: T & P> either. At first blush, it doesn't seem like T & P as an existential type would be meaningfully different from moving the P constraint onto the generic parameter like trochoid suggested.
1 Like
bbrk24
7
I’ve definitely wanted this before and have had to fall back to runtime type checking for lack of this. Consider MultitypeService which has four public initializers. All four of them take a collection containing Any.Type, but it is a programming error (and fatalError* at runtime) for any of the types to not be a supertype of the class’s generic type argument.
*In certain code paths, the value is casted to the specified type(s) with as!, which would fail.