Witness table vs vtable — в чём разница?
Суть
Обе — таблицы указателей на функции, через которые идёт табличная диспетчеризация, но отвечают они на разные вопросы:
- vtable — «какую реализацию метода использовать для этого класса». Одна таблица на класс, создаётся компилятором для всех переопределяемых методов (не
final, неstatic, не из extension). Подкласс получает копию таблицы родителя, в которой слоты переопределённых методов указывают на новые реализации, а новые методы добавлены в конец. Вызов: взять metadata объекта → vtable → слот по фиксированному индексу →call. - witness table — «как этот тип выполняет требования этого протокола». Одна таблица на пару (тип, протокол), по слоту на каждое требование протокола (методы, свойства, associated types). Существует и для value-типов, и для классов. Вызов через
any Pили неспециализированныйT: P: контейнер/метаданные → witness table → слот →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() // vtable класса Dog
_ = areaGeneric(Circle(r: 1)) // witness table (или специализация)
_ = areaExistential(Circle(r: 1)) // existential → witness table
Продолжение ответа с кодом и уточняющими вопросами интервьюера — в приложении DevPrep.
Полный ответ — в DevPrep