Variance in protocols

I have a code like this one.

class Base {}
class Derived: Base {}
protocol A {var a: Base {get}}
protocol B {var a: Derived {get}}
struct Theta: A, B {var a: Base{Derived()}}

Sadly, there is an error: Type 'Theta' does not conform to protocol 'B'. First of all, it's bogus. Since every Derived is Base it should be sufficient to satisfy protocol requirement. Which is proven by the following code being complied without errors:

protocol AA { var a: Int {get}}
protocol BB { var a: Int {get}}
struct Nu: AA, BB {var a: Int{0}}

Where can I find the exhaustive explanation about subtyping rules for the language?

Ps. the following doesn't work also.

protocol E {var a: Any {get}}
struct Tau: E, AA { var a: Int{0}}
1 Like

It is not bogus but very much correct, and your reasoning is on the right track but backwards. Theta.a is a variable of type Base; that means it can be set to some other value of type Base that isn't of type Derived. Therefore, it can't fulfill the protcol requirements of B.

2 Likes

But, if Theta.a was a Derived instead, it should work?

Protocol requirements (currently) need to have the exact type, not a subtype thereof. It won't work even if Theta.a is Derived since A.a won't be satisfied.

1 Like

You can use the currently unofficial @_implements attribute to annotate a member as an implementation of a protocol requirement. This is particularly useful in this case where a single type needs to implement two different protocols with overlapping, but incompatible, protocol requirements:

struct Theta: A, B {
    @_implements(A, a)
    var aA: Base { Derived() }
    @_implements(B, a)
    var aB: Derived { Derived() }
}

Note that in a context where a Theta is passed as an A (eg. when passed as an argument to a function that expects an A), calling .a will be dispatched to .aA. And contrary to .aB when called on an instance of type B. This might be confusing for a user, consider this:

protocol A { var a: String {get} }
protocol B { var a: Int {get} }

struct Theta: A, B {
    @_implements(A, a)
    var aA: String { "Hello" }
    @_implements(B, a)
    var aB: Int { 42 }
}
Theta().a  // ERROR: ambiguous use of 'aB'
Theta().a as String // "Hello"
Theta().a as Int // 42
func printA(_ a: A) {
    print(a.a)
}
printA(Theta()) // "Hello"

Also note that @_implements is an underscored attribute and not officially supported. It may disappear or be renamed at any point, or its semantics may be changed without notice. However, I'm not sure if the attribute only affects compilation or if it has runtime consequences. That is, I don't know it using it is only running the risk of making code incompilable in the future, or if it also is running the risk of making compiled code unexecutable.

4 Likes

Feels like a bug.

Terms of Service

Privacy Policy

Cookie Policy