SwiftQA Random

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:

  1. Inline value buffer — 3 words (24 bytes on 64-bit)
  2. A pointer to the type’s metadata
  3. 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

  1. Heap allocation for anything over 24 bytes.
  2. Indirect calls through the witness table on every method invocation — defeats inlining.
  3. Copying is never free even in the inline case — container bytes plus possible retain/release.
  4. Existential arrays are the classic real-world hit — hostile to cache locality vs. [Circle].
  5. Self and 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?YesNo
StorageInline, sized to actual concrete typeFixed 3-word container, heap-boxed if oversized
DispatchDirect call if specialized; witness table if notAlways witness table
Heap allocationNone (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.”