Which method runs through a protocol extension: when is a call dispatched dynamically and when statically?

27.5.8 · L3 · Interview

Predict first

What does this print?

protocol Greeter { func hello() -> String }
extension Greeter {
    func hello() -> String { "default hello" }
    func bye() -> String { "default bye" }
}
struct En: Greeter {
    func hello() -> String { "en hello" }
    func bye() -> String { "en bye" }
}
let e = En()
let g: Greeter = En()
print(e.hello(), e.bye())
print(g.hello(), g.bye())
class Animal { func sound() -> String { "..." } }
extension Animal { func kind() -> String { "animal" } }
class Dog: Animal { override func sound() -> String { "woof" } }
let a: Animal = Dog()
print(a.sound(), a.kind())
protocol Shape { var name: String { get } }
extension Shape { var name: String { "shape" } }
struct Sq: Shape { var name: String { "square" } }
struct Ci: Shape {}
func printName<S: Shape>(_ s: S) { print(s.name) }
printName(Sq()); printName(Ci())
class Base: Greeter { }
class Derived: Base { func hello() -> String { "derived hello" } }
let b: Greeter = Derived()
print(b.hello(), Derived().hello())
func viaGeneric<T: Greeter>(_ t: T) -> String { t.hello() + "/" + t.bye() }
print(viaGeneric(En()), viaGeneric(g as! En))
  • en hello en bye / en hello default bye / woof animal / square / shape / default hello derived hello / en hello/default bye en hello/default bye
  • en hello en bye / en hello en bye / woof animal / square / shape / derived hello derived hello / en hello/en bye en hello/en bye
  • en hello en bye / en hello default bye / woof animal / shape / shape / default hello derived hello / en hello/default bye en hello/default bye

Mental model. A company phone directory: requirements are listed in the official directory, extras are only in the sender's own address book.

A method that is a protocol requirement is dispatched through the witness table, so the conforming type's version wins even through the protocol type: g.hello() is "en hello" and the generic printName prints "square". A method that exists only in the extension (bye) is dispatched statically from the declared type: g.bye() on a Greeter gives "default bye" although En has its own, while e.bye() on the struct gives "en bye". Extension methods on a class (kind()) are not overridable either.

The last case: Base: Greeter uses the default hello; Derived.hello() is a new method, not an override, because the witness is recorded for Base. Through Greeter or a generic constraint you get "default hello"; only a direct Derived call gets "derived hello". Put every customisation point in the protocol itself (Q61, Q190).

Only protocol requirements are dispatched dynamically.

Common traps

  • Adding a method only in an extension and expecting overrides to work. Add it to the protocol.
  • Defining a method in a subclass of a conforming class and expecting it to satisfy the protocol. It does not.

They might follow up with

  • How do you see it? Check the SIL witness tables (Q190).

Related questions

  • 1.11.1 Static vs dynamic dispatch in Swift?
  • 5.1.4 How do you read SIL to tell static dispatch from dynamic dispatch?

← In what order are operands, default arguments, autoclosures and…   How does synthesised Decodable treat missing keys, nulls, default… →