Why repeating protocol conformance in a subclass is considered redundant?

Repeating protocol conformance in a subclass of conforming class results in a redundancy error. My problem is that, because of how protocol witness tables work, repeating protocol conformance in a subclass seems not to be redundant at all . Intuitively, in the Example3 below repeating a conformance of the parent class in the child class could restore the route from protocol extension's implementation back to child's implementation . Where am I wrong?

protocol Prot {
    func f()
    func g()
}
extension Prot {
    func f(){ print("protocol extension's implementation") }
    func g(){ f() }
}
class Parent: Prot {
}

//Directly implementing protocol would route to a child's implementation.
class Example1: Prot {
    func f(){ print("child's implementation")}
}

//Indirectly implementing protocol would route to a protocol extension's implementation.
class Example2: Parent {
    func f(){ print("child's implementation")}
}

//Redundant conformance of 'Child' to protocol 'Prot' error, instead of restoring route to a child's implementation.
class Example3: Parent, Prot {
    func f(){ print("child's implementation")}
}

Example1().g()
Example2().g()
Answered by fallthrough in 739953022

From Kavon F(Apple), during Ask Apple

So, this interaction with default protocol witnesses has been reported before here. If you want the ability to override the Parent class's default witness to the f() requirement that was provided by Prot, the Parent , who is the actual conformer of the protocol, currently must provide its own witness so that the subclass (e.g., Example3 ) can use a standard method override to change the Parent behavior after the fact. Protocols don't support inheritance, so they're choosing the methods directly listed in the type that wants to conform (or using defaults). Since a subclass can always be substitued in places where the superclass type is expected, the subclass is considered to be already conforming to the protocol. Thus, the conformance listed in the subclass is currently considered meaningless. It could mean that, and to make it happen, a pitch on Swift Evolution would be a way to start the conversation.

If you implement f() in Parent, which one should be selected in Example3 ?

  • Example1().g() -> child's implementation
  • Example2().g() -> protocol extension's implementation
  • Example3().g() -> child's implementation again
  • Example3().g(), with func f() also in Parent -> (Error) missing 'override' keyword
  • Example3().g(), with func f() in Parent, and override func f() in child -> child's implementation
  • Example3().g(), with func f() in Parent, and func f() removed from a child -> parent's implementation

In this hypothetical example, when both Parent and Child explicitly declare conformance to a protocol, their behaviour is more consistent compared to when only Parent explicitly declare conformance.

May be you could ask the question on Swift forum, on the exact reason for Protocol extensions behaving this way. And please post the answer here if you get one.

The short answer here is that it just doesn't work that way, sorry. :)

For functions that are protocol requirements (both f and g in your example), regardless of whether there are implementations in the protocol extension, those functions are dynamically dispatched. This means that calling one of these functions works like in a traditional class hierarchy, where the runtime "search" for the correct function to call starts at the runtime type of the class instance involved — which is Example3 in this case.

The search proceeds up the superclass chain as you'd expect, then proceeds from the base class up to the protocol to which it conforms (and again on up to any other protocols that the protocol conforms to). So, the search for g() follows the path Example3 -> Parent -> Prot. Then, for f() the same search path is used: Example3 -> Parent -> Prot. This is all by design.

The problem with having different semantics, when Example3 conforms to Prot again (that is, as well as the Parent conformance), would be that you'd now have a multiple-inheritance graph, which is something that Swift does not support. Therefore, the additional conformance is not allowed, and (I guess) the error message calls it redundant because the usual reason for this error is an accidental re-conformance.

So, that's why the re-conformance is not allowed. I hope that the explanation is clear.

It's also worth understanding that, for functions defined in a protocol extension that are not requirements of the protocol:

extension Prot {
    func h() { … }
}

those functions are not dispatched dynamically. Instead, they are dispatched based on the compile-time type of the expression calling the function:

let p1: Prot = Example3()
let p2: Parent = Example3()
let p3: Example3 = Example3()
p1.h() // <- always calls the function declared in the protocol extension, assuming there is one
p2.h() // <- always calls the function declared in the `Parent` class, assuming there is one
p3.h() // <- always calls the function declared in the `Example3` class, assuming there is one

The problem here is that, when h() is not declared in all 3 places, the actual behavior is not very well defined in Swift. Trying to rely on overload resolution here is emphatically to be avoided, especially when there is a mixture of statically- and dynamically-dispatched functions.

Accepted Answer

From Kavon F(Apple), during Ask Apple

So, this interaction with default protocol witnesses has been reported before here. If you want the ability to override the Parent class's default witness to the f() requirement that was provided by Prot, the Parent , who is the actual conformer of the protocol, currently must provide its own witness so that the subclass (e.g., Example3 ) can use a standard method override to change the Parent behavior after the fact. Protocols don't support inheritance, so they're choosing the methods directly listed in the type that wants to conform (or using defaults). Since a subclass can always be substitued in places where the superclass type is expected, the subclass is considered to be already conforming to the protocol. Thus, the conformance listed in the subclass is currently considered meaningless. It could mean that, and to make it happen, a pitch on Swift Evolution would be a way to start the conversation.

Why repeating protocol conformance in a subclass is considered redundant?
 
 
Q