Что такое existential container (any P)? Почему any дороже, чем T: P?
Суть
any P — значение неизвестного на компиляции типа, поэтому компилятор упаковывает его в existential container фиксированного размера:
- inline value buffer — 3 машинных слова (24 байта на arm64);
- указатель на type metadata (какой тип внутри: размер, выравнивание, как копировать/уничтожать);
- указатель на witness table для каждого протокола в композиции (
any A & B— два указателя).
Итого any P занимает 40 байт. Если значение влезает в буфер — лежит инлайн; если нет — аллоцируется в куче, а в буфере хранится ссылка (boxing). Класс-экземпляр всегда помещается: это одна ссылка. Для class-bound протокола (any P & AnyObject) контейнер короче: ссылка + witness table, 16 байт, metadata берётся из объекта.
protocol P {}
struct Small: P { var a, b, c: Int } // 24 байта — помещается в буфер
struct Big: P { var a, b, c, d: Int } // 32 байта — уедет в кучу (boxing)
final class Obj: P {}
print(MemoryLayout<Small>.size, MemoryLayout<Big>.size) // 24 32
print(MemoryLayout<any P>.size) // 40
print(MemoryLayout<any P & AnyObject>.size) // 16
let values: [any P] = [
Small(a: 1, b: 2, c: 3), Big(a: 1, b: 2, c: 3, d: 4), Obj()
]
Продолжение ответа с кодом и уточняющими вопросами интервьюера — в приложении DevPrep.
Полный ответ — в DevPrep