What are existentials and why are they expensive?
What an existential actually is
An existential is a value whose concrete type is erased and can vary at runtime, wrapped in a container. In Swift, any Protocol is an existential type:
protocol Shape { func area() -> Double }
struct Circle: Shape { func area() -> Double { ... } }
struct Square: Shape { func area() -> Double { ... } }
var shape: any Shape = Circle()
shape = Square() // same variable, different concrete type at runtime — legal
The existential container — the actual mechanism
Existential values are represented as a fixed-size container with:
- Inline value buffer — 3 words (24 bytes on 64-bit)
- A pointer to the type’s metadata
- A pointer to the protocol witness table
If the value fits in 3 words, it’s stored directly. If not, the runtime heap-allocates a box.
Why this actually costs performance
- Heap allocation for anything over 24 bytes.
- Indirect calls through the witness table on every method invocation — defeats inlining.
- Copying is never free even in the inline case — container bytes plus possible retain/release.
- Existential arrays are the classic real-world hit — hostile to cache locality vs.
[Circle]. Selfand associated-type requirements often can’t be existentials at all.
Contrast with generics
Generic <T: Shape> | Existential any Shape | |
|---|---|---|
| Concrete type fixed per call/context? | Yes | No |
| Storage | Inline, sized to actual concrete type | Fixed 3-word container, heap-boxed if oversized |
| Dispatch | Direct call if specialized; witness table if not | Always witness table |
| Heap allocation | None (unless the type itself needs it) | Only if type > 24 bytes |
When existentials are actually the right tool
- Heterogeneous collections
- Delegate/handler patterns
- Plugin/extensibility points
If the concrete type is actually fixed for a given context, prefer generics or some Protocol.
One-liner if pressed: “Existentials erase the concrete type into a runtime-checked container — 3 words inline, heap-boxed beyond that, with witness-table indirection on every method call — which is the necessary cost of letting a single variable hold different conforming types over its lifetime; if the type is actually fixed per context, generics or some get you the same abstraction without paying that cost.”