I have a problem with generics in swift. Let\'s expose my code.
protocol FooProtocol {
associatedtype T
}
protocol Fooable { }
extension Int : Fooable { }
Is the following what you tried to achieve? (compiled & tested with Xcode 11.4)
func createOne() -> some FooProtocol {
let anyFoo = AnyFoo(p: FooImpClass())
return anyFoo
}
EDIT to respond to the edit to the question:
createTwo
doesn't work because you have the same misconception as I said in my original answer. createTwo
decided on its own that F
should be either String
or Int
, rather than "any type that conforms to Fooable
".
For createOne
, you have another common misconception. Generic classes are invariant. AnyFoo<String>
is not a kind of AnyFoo<Fooable>
. In fact, they are totally unrelated types! See here for more details.
Basically, what you are trying to do violates type safety, and you redesign your APIs and pick another different approach.
Original answer (for initial revision of question)
You seem to be having a common misconception of generics. Generic parameters are decided by the caller, not the callee.
In createOne
, you are returning anyFoo
, which is of type AnyFoo<Int>
, not AnyFoo<P>
. The method (callee) have decided, on its own, that P
should be Int
. This shouldn't happen, because the caller decides what generic parameters should be. If the callee is generic, it must be able to work with any type (within constraints). Anyway, P
can't be Int
here anyway, since P: FooProtocol
.
Your createOne
method should not be generic at all, as it only works with Int
:
func createOne() -> AnyFoo<Int> {
let anyFoo = AnyFoo(p: FooImpClass())
return anyFoo
}
EDIT
I finally managed to keep your where clause
:)
EDIT Still not sure what you want to do, and I still agree with @Sweeper but I love to badly abuse generics :) :
protocol FooProtocol {
associatedtype T
init()
}
protocol Fooable { }
extension Int : Fooable { }
extension String: Fooable { }
class AnyFoo<T>: FooProtocol {
init<P: FooProtocol>(p: P) where P.T == T { }
init<T>(p: T.Type) { }
required init() { }
}
class FooIntImpClass: FooProtocol {
typealias T = Int
required init() { }
}
class FooStringImpClass: FooProtocol {
typealias T = String
required init() { }
}
func createOne<F: FooProtocol>(foo: F.Type) -> AnyFoo<F.T> {
let anyFoo = AnyFoo<F.T>(p: F.init())
return anyFoo
}
func createTwo<F: FooProtocol>(foo: F.Type) -> some FooProtocol {
let anyFoo = AnyFoo<F.T>(pk: F.T.self)
return anyFoo
}
that compiles but I don't know what to do with it.
Edit
yeah I really don't know:
let one = createOne(foo: FooStringImpClass.self) // AnyFoo<String>
print(type(of: one).T) // "String\n"
let two = createTwo(foo: FooIntImpClass.self) // AnyFoo<Int>
print(type(of: two).T) // "Int\n"
Is that what you wanted ?
where
clause on the AnyFoo
class instead of it's initializer.
And I should add, that your where clause on the initializer was wrong, Like Sweeper said :
Anyway, P can't be Int here anyway, since P: FooProtocol.
The following code compiles :
protocol FooProtocol {
associatedtype T
}
class AnyFoo<T>: FooProtocol where T: FooProtocol {
init<P: FooProtocol>(p: P) { }
}
class FooImpClass: FooProtocol {
typealias T = Int
}
func createOne<P: FooProtocol>() -> AnyFoo<P> {
let anyFoo: AnyFoo<P> = AnyFoo(p: FooImpClass())
return anyFoo
}