It is not possible to use a protocol with a property with Self as type as a type, container type, parameter. I think I need an example where it makes sense, that the compiler can not infer the type.
internal protocol Lovable {
var inLoveTo: Self? {
get
}
}
internal final class Human: Lovable {
var inLoveTo: Human? = nil
}
internal final class Animal: Lovable {
var inLoveTo: Animal? = nil
}
internal let thing11: Human = Human()
internal let thing12: Animal = Animal()
But the following code makes sense to me and could work. So there have to be a case where you can not infer the type at compile time, which I can not see yet.
import Darwin
let thing13: Lovable = Darwin.random() % 2 == 0 ? thing11 : thing12 // So you do not know which dynamicType thing13 has at compile time, but it should be Lovable, error: mismatching types
thing13.inLoveTo // It could be Lovable
// Does not work, even though it makes sense for me, since inLoveTo adopts Lovable
internal func partner(one: Lovable) -> Lovable {
return one.inLoveTo
}
What do I not see?
protocol Foo {
}
final class Bar1: Foo {
let bla: Int8 = 100
}
final class Bar2: Foo {
let bla: Int64 = 600000
}
internal let thing21: Foo = Bar1()
internal let thing22: Foo = Bar2()
internal let thing23: Foo = Darwin.random() % 2 == 0 ? thing21 : thing22 // So you do not know which type it has at compile time
Self
refers to the runtime type of whatever is implementing that protocol. The problem is that when you have a function that takes a Lovable
input (or a variable with an explicit annotation of Lovable
), you're upcasting to the abstract type Lovable
.
By doing this you're losing the type information for what Self
was. Was it an Animal
or a Human
? This is needed by the compiler in order to use the protocol as a type, as it has a property of type Self
, which cannot be resolved. This therefore means you cannot use a protocol with Self
or associatedtype
requirements as an actual type, you can only ever use it as a generic constraint.
One potential solution is to change the property in your protocol to be of type Lovable
. Now you're saying that everything that conforms to Lovable
has a property of 'some other' Lovable
. You now don't need to know the concrete type of whatever that 'something' is, although this will break an important relationship that you want to establish (two partners must be of the same type!)
One way to maintain this relationship (at least for your function) is to use generics. The reason why using generics works is they act as a placeholder for a concrete type, which is supplied to the function when you call it. Now your function input knows what Self
is – it's whatever concrete type is supplied to the function.
You could make your partner
function generic like so:
func partner<T:Lovable>(one: T) -> T? {
return one.inLoveTo
}
This also guarantees that the return type of the function is of the same concrete type as the input (albeit wrapped in an optional), providing better type safety.
Unfortunately for your variable assignment, there's no real solution that doesn't involve breaking the relationship you've established (inLoveTo
must be the same type as the class). You're trying to tie a runtime decision (Human
or Animal
could be assigned) to a static concrete type that's given at compile time, which cannot work.
As you've noted, using Self
as a return type of a function doesn't make the protocol generic. I believe this is due to variance – as you can always pass a subclass to something that expects its superclass. Therefore as the return type of the function will match the current static type of your instance, the static type can only ever refer to a less specific type than the dynamic type of the instance. Therefore you can freely return an instance with the same dynamic type, as the static type that describes it can only be less type specific.
This behaviour is different with properties as the protocol has limited control over how they are implemented. {get}
can be implemented as any kind of property, {get set}
can be implemented as a stored or calculated property with a setter. In both of these cases, the property is potentially able to be set in the conforming class. Now we run into the original problem. Self
is the concrete type of the class, therefore we have to know that concrete type in order to assign to it, which you lose upon upcasting.
You cannot simply treat the property as being a Loveable
as that would allow any conforming instance to be assigned to it, i.e assigning an Animal()
to a Human
property – which is illegal.
Collected from the Internet
Please contact [email protected] to delete if infringement.
Comments