Covariant 'Self'

It seems odd that there exist limitations for using Self in a covariant context on a concrete type, such as:

class A {
    @Published<Self?> // Covariant 'Self' type cannot be referenced from a stored property initializer
    var a: Self? // Mutable property cannot have covariant 'Self' type
}

or

class A {
    typealias _Self = Self // Covariant 'Self' or 'Self?' can only appear as the type of a property, subscript or method result; did you mean 'A'?
    @Published<_Self?>
    var a: _Self?
}

when there seems to be no issue whatsoever in doing this through a protocol-defined typealias of Self:

protocol P {
    typealias _Self = Self
}
class A: P {
    @Published<_Self?>
    var a: _Self?
}

What are the practicalities involved in these limitations, are there special considerations that set the latter example apart, and could the limitations be lifted?

> print(type(of: A().a))

Optional<A>

Within a protocol, Self refers generically to the conforming type. You can see how this Self behaves differently from covariant Self in classes by declaring a subclass of A:

protocol P {
    typealias _Self = Self
}
class A: P {
    @Published<_Self?>
    var a: _Self?
    var a2: Self? { return nil } // 'covariant Self'
}

class B: A {

}

print(type(of: B().a))  // Optional<A>
print(type(of: B().a2)) // Optional<B>
1 Like

Thank you. Perhaps someone can speak to the limitations at play and potential for relaxing them?

The limitations in the general case are that 'covariant Self' can only be used in positions that are actually covariant in the type of Self. This applies to non-mutable properties such as a2 above because if you have a value b of type B it can be simultaneously true that b.a2 has type B and (b as A).a2 has type A.

However, in the case of generic parameters (or mutable properties), this is not the case. Just focusing on mutable properties, consider:

class A {
  var a: Self
}
class B: A {
  func doBThings() { ... }
}
let b = B(a: B())
(b as A).a = A()
b.doBThings() // ??

Because a set operation is contravariant with respect to the property type, it is not the case that b.a has type B while (B as A).a has type A, because it's not valid to set b.a to arbitrary values of type A. Specifically, the only values of type A that we can set b.a to are those that are also of type B. So 'contravariant Self' doesn't work for these properties. The same issue arises with generic parameters because in the general case, the generic parameter of type Self may be used to both produce values of type Self (a covariant operation), consume values of type Self (a contravariant operation), or both simultaneously (an invariant operation).

The potential for relaxing here would probably amount to letting you specify the variance of your generic parameter, but I'm not sure if that's something we'd want generally.

1 Like