Witness table vs vtable — what's the difference?
The idea
Both are tables of function pointers through which table dispatch goes, but they answer different questions:
- vtable — "which implementation of a method to use for this class". One table per class, built by the compiler for every overridable method (not
final, notstatic, not from an extension). A subclass gets a copy of its parent's table in which the slots of overridden methods point to the new implementations and new methods are appended. A call: take the object's metadata → vtable → slot at a fixed index →call. - witness table — "how this type fulfils the requirements of this protocol". One table per (type, protocol) pair, with a slot for each protocol requirement (methods, properties, associated types). Exists for value types and classes alike. A call through
any Por an unspecializedT: P: container/metadata → witness table → slot →call.
protocol Shape { func area() -> Double }
struct Circle: Shape {
let r: Double
func area() -> Double { .pi * r * r }
}
class Animal { func sound() -> String { "..." } }
class Dog: Animal { override func sound() -> String { "Woof" } }
func areaGeneric<S: Shape>(_ s: S) -> Double { s.area() }
func areaExistential(_ s: any Shape) -> Double { s.area() }
let pet: Animal = Dog()
_ = pet.sound() // Dog's vtable
_ = areaGeneric(Circle(r: 1)) // witness table (or specialization)
_ = areaExistential(Circle(r: 1)) // existential → witness table
The rest of this answer, with code and interview follow-ups, is in the DevPrep app.
Read the full answer in DevPrep