You added a method in a protocol extension and overrode it on a concrete type, but calling through any P does not hit your override. Why?

1.11.3 · L5 · Interview

Predict first

What does this print?

protocol Greeter {}
extension Greeter {
    func hello() -> String { "protocol" }
}

struct Friendly: Greeter {
    func hello() -> String { "struct" }
}

let g: any Greeter = Friendly()
print(g.hello())
  • struct
  • protocol

Mental model. The protocol is the contract. Your extension method is an appendix nobody signed — so nobody is obliged to use it.

Because the method is not a protocol requirement. Only requirements get a slot in the witness table. A method that exists only in an extension is a plain static function, and the call is bound to the extension at compile time.

So p.describe() where p: any P calls the extension's implementation — your concrete override is invisible on that path.

Fix: declare the method in the protocol itself and provide the default in an extension. Then it gets a witness-table entry and the concrete implementation runs.

hello() is not a protocol requirement, so it is not in the witness table. Through any Greeter the call is resolved statically to the extension. The lab below lets you flip that.

Common traps

  • “The override must be dead code.” It is called when the static type is the concrete type. The bug appears only on the existential or generic path — hence the intermittent reports.
  • “Swift should warn about this.” It does not; this is valid, intentional language behaviour. Only a requirement gets dynamic dispatch.

They might follow up with

  • What about a class conforming to a protocol? Same rule — if the method is not a requirement, no witness entry, no override.
  • How do you test for it? Call the method through any P and through the concrete type; assert they match.

Related questions

  • 1.3.3 Protocol extension vs inheritance for default behaviour — what are the tradeoffs?
  • 1.11.1 Static vs dynamic dispatch in Swift?
  • 1.3.8 How do you evolve a public protocol without breaking clients?

← Why does final (and whole-module optimization) help performance?