What is an existential container (any P)? Why is any costlier than T: P?
The idea
any P is a value whose type is unknown at compile time, so the compiler packs it into a fixed-size existential container:
- an inline value buffer — 3 machine words (24 bytes on arm64);
- a pointer to type metadata (what type is inside: size, alignment, how to copy/destroy);
- a pointer to a witness table for each protocol in the composition (
any A & B— two pointers).
In total any P takes 40 bytes. If the value fits the buffer it is stored inline; if not, it is allocated on the heap and the buffer holds a reference (boxing). A class instance always fits: it is one reference. For a class-bound protocol (any P & AnyObject) the container is shorter: reference + witness table, 16 bytes; metadata comes from the object.
protocol P {}
struct Small: P { var a, b, c: Int } // 24 bytes — fits the buffer
struct Big: P { var a, b, c, d: Int } // 32 bytes — heap (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()
]
The rest of this answer, with code and interview follow-ups, is in the DevPrep app.
Read the full answer in DevPrep