Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt

Use this file to discover all available pages before exploring further.

An existential type in Swift is a dynamically dispatched type that represents any concrete type conforming to a specific protocol or protocol composition. It abstracts away the underlying concrete type at compile time, deferring type resolution to runtime. In type theory, it asserts that “there exists” some concrete type satisfying the protocol requirements, without statically exposing what that type is. Since Swift 5.6, existential types are explicitly denoted using the any keyword.
protocol Renderable {
    func render()
}

struct Polygon: Renderable {
    func render() { /* ... */ }
}

// 'any Renderable' is the existential type
let shape: any Renderable = Polygon() 

Memory Layout: The Existential Container

Because the compiler does not know the size of the underlying concrete type at compile time, it cannot allocate a fixed amount of stack space directly. Instead, Swift wraps the value in an internal compiler structure called an Existential Container. For standard (non-class-bound) protocols, Swift uses an Opaque Existential Container, which is exactly 5 machine words (40 bytes on a 64-bit system) in size:
  1. Value Buffer (3 words): If the concrete value fits within 3 words (24 bytes), it is stored inline. If it exceeds 3 words, Swift allocates memory on the heap, stores the value there, and places the pointer in the first word of the buffer.
  2. Value Witness Table (VWT) Pointer (1 word): A pointer to a table of function pointers that manage the memory lifecycle of the concrete type (allocation, copying, destruction).
  3. Protocol Witness Table (PWT) Pointer (1 word): A pointer to a table of function pointers corresponding to the protocol’s requirements. This enables dynamic dispatch.
// Conceptual C-representation of Swift's Opaque Existential Container
struct OpaqueExistentialContainer {
    void *valueBuffer[3];        // Inline storage or heap pointer
    ValueWitnessTable *vwt;      // Memory management functions
    ProtocolWitnessTable *pwt;   // Dynamic dispatch table
};
For class-bound protocols (protocol Foo: AnyObject), Swift uses a Class Existential Container, which is smaller (typically 2 words) because the value is guaranteed to be a heap-allocated reference. It only requires the instance pointer and the Protocol Witness Table pointer.

Type System Mechanics

Dynamic Dispatch

When a method is invoked on an existential type, the compiler cannot use static dispatch. Instead, it reads the Protocol Witness Table (PWT) from the existential container, looks up the memory address of the concrete implementation, and jumps to it. This indirection incurs a runtime performance cost compared to static dispatch.

Type Erasure and Protocol Conformance

An existential type acts as a box. The box itself does not automatically conform to the protocol it represents. While modern Swift can implicitly “open” an existential when passed to a generic function expecting a single instance, collections of existentials expose this limitation.
protocol Computable {
    func compute()
}

struct Calculator: Computable {
    func compute() {}
}

// A generic function requiring a concrete type T that conforms to Computable
func processAll<T: Computable>(items: [T]) {
    for item in items { item.compute() }
}

// An array of existential boxes
let items: [any Computable] = [Calculator()]

// Error: Type 'any Computable' cannot conform to 'Computable'
// processAll(items: items)

Associated Types and Self Requirements

Historically, protocols with associatedtype or Self requirements could not be used as existential types because the compiler could not guarantee type safety at runtime. Swift 5.7 introduced constrained existential types, allowing you to specify primary associated types within angle brackets.
struct User {}

protocol Fetcher<Model> {
    associatedtype Model
    func fetch() -> Model
}

struct APIUserFetcher: Fetcher {
    func fetch() -> User { 
        return User() 
    }
}

// Constrained existential type
let userFetcher: any Fetcher<User> = APIUserFetcher()
Even with constrained existentials, the underlying type is still erased. The compiler knows the Model is User, but it still does not statically know the concrete type of the Fetcher itself.

Existential Types vs. Opaque Types

Existential types (any Protocol) are often contrasted with opaque types (some Protocol).
  • any Protocol (Existential): Type resolution is deferred to runtime. The variable can hold different concrete types at different times. It utilizes dynamic dispatch and existential containers.
  • some Protocol (Opaque): Type resolution occurs at compile time. The compiler knows the exact concrete type, but hides it from the API consumer. It utilizes static dispatch and requires no existential container.
struct Circle: Renderable {
    func render() { /* ... */ }
}

// Existential: Can return different concrete types at runtime
func getAny() -> any Renderable {
    return Bool.random() ? Polygon() : Circle()
}

// Opaque: Must return the exact same concrete type on every path
func getSome() -> some Renderable {
    return Polygon() // Cannot conditionally return Circle()
}
Master Swift with Deep Grasping Methodology!Learn More