Reputation: 310
Say, I have a protocol with an associated type:
protocol Model {
associatedtype ModelType
}
And there's a generic struct conforming to this protocol:
struct Implementation<T>: Model {
typealias ModelType = T
}
Now, I want to add an instance method to this struct accepting and returning a value of the abstract Model
type:
struct Implementation<T>: Model {
typealias ModelType = T
func test<M: Model>(model: M) -> M {
return model
}
}
This compiles without errors. I want to go further and make sure that both the model
parameter and the return value of the test(model:)
method have the same associated ModelType
as the Implementation
instance this method is called on:
struct Implementation<T>: Model {
typealias ModelType = T
func test<M: Model>(model: M) -> M where M.ModelType == T {
return model
}
}
This compiles without errors, and it works as expected. If there's a second type conforming to Model
…
struct Alternative<T>: Model {
typealias ModelType = T
}
…, I can pass an instance of this type to the test(model:)
method only if it has the same associated ModelType
. That is, the following code compiles…
let implementation = Implementation<Int>()
let alternativeA = Alternative<Int>()
_ = implementation.test(model: alternativeA)
…, whereas the following code:
let implementation = Implementation<Int>()
let alternativeB = Alternative<String>()
_ = implementation.test(model: alternativeB)
… fails to compile with an error saying,
Instance method 'test(model:)' requires the types 'Int' and 'Alternative<String>.ModelType' (aka 'String') be equivalent
Now, under some condition, the test(model:)
method should return self
. If I change it to (always) return self
, however…
struct Implementation<T>: Model {
typealias ModelType = T
func test<M: Model>(model: M) -> M where M.ModelType == T {
return self
}
}
…, I'll get an error saying,
Cannot convert return expression of type 'Implementation<T>' to return type 'M'
This is confusing, as, clearly, the type of self
should match all given constraints. (Or doesn't it?)
The error is, in fact, unrelated to the where
clause. If I drop it…
struct Implementation<T>: Model {
typealias ModelType = T
func test<M: Model>(model: M) -> M {
return self
}
}
…, the error will persist:
Cannot convert return expression of type 'Implementation<T>' to return type 'M'
Why am I getting this error, and how can I return self
from this method?
(I'm using Swift 5.3.2 by the way in case this matters.)
Upvotes: 0
Views: 446
Reputation: 259
Generic functions always try to infer type from the context and at compile time
// func test<M: Model>(_ model: M) -> M
test(Alternative<Int>())
is treated as concrete type function
func test(_ model: Alternative<Int>) -> Alternative<Int>
so u cant do
struct Implementation<T>: Model {
typealias ModelType = T
func test(_ model: Alternative<Int>) -> Alternative<Int> {
return self // Implementation<T>, type mismatch
}
}
Also if you using a generic constraint for your generic function within a type
struct Implementation<T>: Model {
typealias ModelType = T
func test<M: Model>(_ model: M) -> M where M.ModelType == T {
return model
}
}
For Implementation<Int>
translated into something like
extension Implementation where T == Int {
func test<M: Model>(_ model: M) -> M where M.ModelType == Int {
return model
}
}
The constraint prevents you from calling
Implementation<Int>().test(Alternative<String>())
Maybe this proposal will let you fix the issue, but I think it will still be impossible, because this case cannot be resolved in compile time. Proposal
I'm not sure if you really want to return self or model from your method, maybe it's better to pass your model as an inout
parameter.
If you still want to return self you may do something like this
struct Implementation<T>: Model {
typealias ModelType = T
func test<M: Model>(_ model: M) -> Self where M.ModelType == T {
return self
}
}
You may overload this method to let compiler decide (from the context) what to use
struct Implementation<T>: Model {
typealias ModelType = T
func test<M: Model>(_ model: M) -> Self where M.ModelType == T {
return self
}
func test<M: Model>(_ model: M) -> T where M.ModelType == T {
return model
}
}
But it may cause some type inference issues if M == Self
Btw you already creating an existential for your protocol by making a protocol witness
Also I'm not sure if you really need to create a few witness types for your Model
protocol, maybe you can generalize your work just using Implementation<T>
extensions with generic constraints
Upvotes: 1
Reputation: 1069
You are getting this error, because indeed self
does not match all given constraints. By stating func test<M: Model>(model: M) -> M where M.ModelType == T
you only say that this type M
(which is only exists in the context of a call to your test
function) is to be a Model
, which's ModelType
is the same as the ModelType
of self
. That does not mean, however, that Self
and M
are equivalent.
A little example using your types from above:
Assuming self
is an instance of Implementation<Int>
. In the context of a call to test(model:)
, M
could be either of Implementation<Int>
or Alternative<Int>
. Returning self
wouldn't be a problem in the first case, as both instances have the same type. However, in the second case you could assign an Implementation<Int>
to an Alternative<Int>
.
var a = Alternative<Int>()
let x = Implementation<Int>()
// This would assign something of type `Implementation<Int>` to a variable that
// may only contain `Alternative<Int>`.
var a = x.test(model: a)
I am sure there is a method to do what you want to achieve in Swift, but the solution totally depends on what that is.
Upvotes: 1