Reputation: 191
I made a simple sample code using protocol and generic.
I want to call doSomething() inside the foo() function.
The following calls work well, and the logs are printed as I expected.
simpleA.doSomething(item: 300) // print -> [SimpleA] value property: 100, item argument: 300
simpleB.doSomething(item: 400) // print -> [SimpleB] value property: 200.0, item argument: 400.0
The following syntax inside the foo() function results in a compilation error.
simple.doSomething(item: 500)
simple.doSomething(item: 600)
Can't I pass the Int or Double value as a parameter inside doSomething?
protocol SimpleProtocol {
associatedtype MyType
func doSomething(item: MyType)
}
struct SimpleA<T>: SimpleProtocol {
typealias MyType = T
private let value: T
init(value: T) {
self.value = value
}
func doSomething(item: T) {
print("[SimpleA] value property: \(value), item argument: \(item)")
}
}
struct SimpleB<T>: SimpleProtocol {
typealias MyType = T
private let value: T
init(value: T) {
self.value = value
}
func doSomething(item: T) {
print("[SimpleB] value property: \(value), item argument: \(item)")
}
}
class Sample {
func debugMethod() {
let simpleA = SimpleA<Int>(value: 100)
let simpleB = SimpleB<Double>(value: 200)
// It works well
simpleA.doSomething(item: 300) // print -> [SimpleA] value property: 100, item argument: 300
simpleB.doSomething(item: 400) // print -> [SimpleB] value property: 200.0, item argument: 400.0
// It doesn't work well.
foo(simpleA)
foo(simpleB)
}
func foo<P: SimpleProtocol>(_ simple: P) {
// Compile Error
// error: cannot convert value of type 'Int' to expected argument type 'P.MyType'
simple.doSomething(item: 500)
simple.doSomething(item: 600)
simple.doSomething(item: 700)
}
}
Upvotes: 1
Views: 54
Reputation: 535557
Forget about all your other code, and concentrate just on the protocol and on the function foo
. Pretend this is all you've got:
protocol SimpleProtocol {
associatedtype MyType
func doSomething(item: MyType)
}
func foo<P: SimpleProtocol>(_ simple: P) {
simple.doSomething(item: 500)
}
That won't compile. SimpleProtocol is a generic on the placeholder type MyType. You have to resolve that placeholder type. For example:
func foo<P: SimpleProtocol>(_ simple: P) where P.MyType == Int {
simple.doSomething(item: 500)
}
Otherwise, there is no reason for the compiler to believe that 500
can go where a MyType is expected.
That, after all, is what you are doing when you declare simpleA
as a SimpleA<Int>
. You are resolving SimpleA's placeholder type T as Int. And you have also said, in SimpleA, that it resolves SimpleProtocol's MyType to its own T:
typealias MyType = T
So for SimpleA, SimpleProtocol's MyType is explicitly resolved to Int. And in the same way, for SimpleB, SimpleProtocol's MyType is explicitly resolved to Double. But you are not doing anything like that for foo
.
So if you want to be able to call foo
with both simpleA
and simpleB
, you could have two versions of foo
:
func foo<P: SimpleProtocol>(_ simple: P) where P.MyType == Int {
}
func foo<P: SimpleProtocol>(_ simple: P) where P.MyType == Double {
}
Now all the rest of your code compiles. This multiplicity of foo
versions may seem like a pain, and indeed it is, but that is exactly what Swift itself must do; look at how many versions of +
it contains!
To embrace both Int and Double in foo
, use Numeric. You would use it like this:
func foo<P: SimpleProtocol>(_ simple: P) where P.MyType : Numeric {
Now you can get by with just one version of foo
once again.
Upvotes: 2