0
0
Swiftprogramming~15 mins

Existential types (any keyword) in Swift - Deep Dive

Choose your learning style9 modes available
Overview - Existential types (any keyword)
What is it?
Existential types in Swift let you work with values of any type that conforms to a certain protocol, without specifying the exact type. The 'any' keyword explicitly marks a type as existential, meaning it can hold any value that fits the protocol. This helps write flexible code that can handle different types uniformly. It is a way to say, "I don't care what exact type this is, as long as it follows these rules."
Why it matters
Without existential types, you would need to know the exact type of every value you work with, which limits flexibility and code reuse. Existentials let you write functions and data structures that can handle many types through a common interface, making your code more adaptable and easier to maintain. Without them, programs would be more rigid and repetitive, forcing you to duplicate code for each type.
Where it fits
Before learning existential types, you should understand Swift protocols and basic type system concepts. After mastering existentials, you can explore advanced topics like opaque types, generics, and protocol-oriented programming to write even more powerful and type-safe code.
Mental Model
Core Idea
Existential types let you say 'any type that fits this protocol' without naming the exact type, enabling flexible and uniform handling of diverse values.
Think of it like...
Imagine a universal remote control that can operate any TV brand as long as it follows a standard remote protocol. You don't need to know the TV's brand, just that it understands the remote's signals.
┌───────────────┐
│ Protocol P    │
│ (set of rules)│
└──────┬────────┘
       │
┌──────▼────────┐
│ Existential   │
│ type: any P   │
│ (any value    │
│ that fits P)  │
└───────────────┘
       ▲
       │
┌──────┴────────┐
│ Concrete types│
│ conforming to │
│ Protocol P    │
└───────────────┘
Build-Up - 7 Steps
1
FoundationUnderstanding Swift Protocols
🤔
Concept: Protocols define a set of rules or methods that types can adopt.
In Swift, a protocol is like a contract. For example: protocol Drawable { func draw() } Any type that says it conforms to Drawable must implement the draw() method.
Result
You can create types that promise to have certain behaviors, making your code more organized.
Knowing protocols is essential because existential types rely on protocols to define what kinds of values they can hold.
2
FoundationWhat is a Type and a Value?
🤔
Concept: Types describe the shape and behavior of data; values are instances of types.
For example, Int is a type, and 5 is a value of type Int. Similarly, a struct or class defines a type, and when you create an object, that's a value.
Result
You understand the difference between the blueprint (type) and the actual thing (value).
This distinction helps grasp why existential types hold values without knowing their exact type.
3
IntermediateIntroducing Existential Types
🤔Before reading on: do you think 'any Drawable' holds one specific type or multiple types at once? Commit to your answer.
Concept: Existential types let you store any value that conforms to a protocol, hiding the exact type behind the protocol interface.
Using 'any Drawable' means a variable can hold any value that implements Drawable, like a Circle or Square, but you don't know which one exactly: var shape: any Drawable shape = Circle() shape.draw() Later, shape can hold a Square instead.
Result
You can write code that works with many types through a common protocol without caring about their concrete types.
Understanding that 'any' wraps different types behind a common interface unlocks flexible and reusable code design.
4
IntermediateExplicit 'any' Keyword Usage
🤔Before reading on: do you think 'any' is optional or required in Swift 5.7+ when using existential types? Commit to your answer.
Concept: Swift 5.7 introduced the explicit 'any' keyword to mark existential types clearly, improving code readability and safety.
Before Swift 5.7, you could write: var shape: Drawable Now, you write: var shape: any Drawable This makes it clear that shape is an existential holding any conforming type, not a generic or concrete type.
Result
Code becomes clearer about when a protocol is used as a type versus a generic constraint.
Knowing the explicit 'any' keyword helps avoid confusion and bugs related to protocol types.
5
IntermediateLimitations of Existential Types
🤔Before reading on: can you call protocol methods with associated types directly on an existential? Commit to your answer.
Concept: Existential types cannot be used with protocols that have associated types or Self requirements directly.
For example, a protocol like: protocol Container { associatedtype Item func getItem() -> Item } cannot be used as 'any Container' because the compiler needs to know the exact Item type. This limitation prevents some protocols from being existential.
Result
You learn that not all protocols can be used with 'any', which guides how you design protocols.
Understanding this limitation prevents confusion and helps design protocols that work well with existentials.
6
AdvancedExistentials vs Generics
🤔Before reading on: do you think existentials and generics are interchangeable? Commit to your answer.
Concept: Existentials and generics both enable abstraction but differ in how they handle types and performance.
Generics let you write code that works with any type, but the exact type is known at compile time: func drawShape(_ shape: T) { shape.draw() } Existentials let you hold values of any conforming type at runtime: func drawShape(_ shape: any Drawable) { shape.draw() } Generics are often faster because the compiler knows the type, while existentials add some runtime overhead.
Result
You understand when to use generics for performance and type safety, and when to use existentials for flexibility.
Knowing the tradeoffs between existentials and generics helps write efficient and maintainable Swift code.
7
ExpertExistential Containers and Memory Layout
🤔Before reading on: do you think existential types store values directly or use pointers internally? Commit to your answer.
Concept: Existential types use a special container that stores the value and a pointer to its type information, enabling dynamic dispatch.
When you assign a value to an 'any' type, Swift wraps it in an existential container: ┌─────────────────────────────┐ │ Existential Container │ │ ┌───────────────┐ │ │ │ Value Storage │ <-- stores the actual value │ └───────────────┘ │ │ ┌───────────────┐ │ │ │ Type Metadata │ <-- points to type info │ └───────────────┘ │ └─────────────────────────────┘ This container enables calling protocol methods dynamically, but adds some overhead compared to static types.
Result
You gain insight into the runtime cost and behavior of existential types.
Understanding the internal container clarifies performance implications and helps debug complex type issues.
Under the Hood
Existential types in Swift are implemented using a container that holds both the value and a pointer to its type metadata. This container enables dynamic dispatch of protocol methods by looking up the correct implementation at runtime. When you assign a concrete value to an existential, Swift copies the value into this container and stores its type information. Calls to protocol methods on the existential use this metadata to invoke the correct method for the underlying type.
Why designed this way?
Swift's designers wanted to balance flexibility and performance. Existentials allow writing flexible code that works with any conforming type, but they come with runtime overhead due to dynamic dispatch. The explicit 'any' keyword was introduced to make this cost and behavior clear to developers, avoiding confusion with generics and improving code readability. Alternatives like generics provide static dispatch but require knowing types at compile time, so existentials fill a unique role.
┌─────────────────────────────┐
│ Existential Container        │
│ ┌───────────────┐           │
│ │ Value Storage │  <-- holds the actual data
│ └───────────────┘           │
│ ┌───────────────┐           │
│ │ Type Metadata │  <-- points to type info and method table
│ └───────────────┘           │
└───────────────┬─────────────┘
                │
                ▼
       ┌─────────────────┐
       │ Protocol Methods │
       │ (dynamic dispatch)│
       └─────────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Does 'any Drawable' mean the variable can hold multiple types at once or only one at a time? Commit to your answer.
Common Belief:Some think 'any Drawable' can hold multiple different types simultaneously.
Tap to reveal reality
Reality:'any Drawable' holds exactly one value of one concrete type at a time, but that type can change over time.
Why it matters:Misunderstanding this leads to incorrect assumptions about type safety and program behavior, causing bugs when switching stored values.
Quick: Is the 'any' keyword optional or mandatory in Swift 5.7+ for existential types? Commit to your answer.
Common Belief:Many believe 'any' is optional and just a style choice.
Tap to reveal reality
Reality:In Swift 5.7 and later, 'any' is required to explicitly mark existential types to avoid ambiguity with generics.
Why it matters:Ignoring this leads to compiler warnings or errors and confusion about whether a protocol is used as a type or a generic constraint.
Quick: Can you use protocols with associated types directly as existential types? Commit to your answer.
Common Belief:Some think all protocols can be used as existential types regardless of associated types.
Tap to reveal reality
Reality:Protocols with associated types or Self requirements cannot be used directly as existential types because the compiler needs concrete type information.
Why it matters:Trying to use such protocols as existentials causes compiler errors and confusion about protocol design.
Quick: Do existentials have zero runtime cost compared to generics? Commit to your answer.
Common Belief:Some believe existentials are as efficient as generics with no overhead.
Tap to reveal reality
Reality:Existentials add runtime overhead due to dynamic dispatch and container storage, unlike generics which use static dispatch.
Why it matters:Ignoring this can lead to performance issues in critical code paths.
Expert Zone
1
Existential containers can store small values inline or use heap allocation for larger values, affecting performance subtly.
2
The explicit 'any' keyword helps distinguish existential types from generic constraints, which is crucial for API clarity and compiler optimizations.
3
Existentials can cause type erasure, which sometimes hides useful type information, making debugging and optimization harder.
When NOT to use
Avoid using existential types when performance is critical and the exact type is known at compile time; prefer generics instead. Also, do not use existentials with protocols that have associated types or Self requirements; instead, use generics or type erasure wrappers.
Production Patterns
In production Swift code, existentials are commonly used for heterogeneous collections, plugin architectures, and APIs where flexibility is more important than raw performance. Developers often combine existentials with type erasure patterns to work around protocol limitations and maintain clean interfaces.
Connections
Generics
Complementary abstraction mechanisms in Swift
Understanding existentials clarifies when to use generics for compile-time type safety and performance versus existentials for runtime flexibility.
Type Erasure
Technique to work around existential limitations
Knowing how existentials cause type erasure helps understand why wrappers like AnySequence exist to enable protocols with associated types.
Interface Segregation Principle (Software Design)
Existentials enforce interface-based programming
Existentials embody the principle of programming to an interface, not an implementation, which improves modularity and maintainability.
Common Pitfalls
#1Using a protocol with associated types directly as an existential type.
Wrong approach:var container: any Container // Compiler error: Protocol 'Container' can only be used as a generic constraint because it has Self or associated type requirements
Correct approach:struct AnyContainer: Container { private let _getItem: () -> T init(_ container: U) where U.Item == T { _getItem = container.getItem } func getItem() -> T { _getItem() } } var container: AnyContainer
Root cause:Misunderstanding that protocols with associated types cannot be used directly as existential types.
#2Omitting the 'any' keyword in Swift 5.7+ when declaring existential types.
Wrong approach:var shape: Drawable // Warning or error: Protocol type 'Drawable' used without 'any' keyword
Correct approach:var shape: any Drawable
Root cause:Not knowing that Swift 5.7 requires explicit 'any' to mark existential types.
#3Assuming 'any Drawable' can hold multiple types simultaneously.
Wrong approach:var shapes: any Drawable = Circle() shapes = Square() // Belief that 'shapes' holds both Circle and Square at once
Correct approach:var shape1: any Drawable = Circle() var shape2: any Drawable = Square()
Root cause:Confusing existential types with collections or union types.
Key Takeaways
Existential types let you work with any value that conforms to a protocol without specifying its exact type.
The 'any' keyword explicitly marks existential types in Swift 5.7 and later, improving clarity and safety.
Existentials enable flexible and reusable code but come with runtime overhead due to dynamic dispatch.
Protocols with associated types cannot be used directly as existential types; use generics or type erasure instead.
Understanding the difference between existentials and generics helps you choose the right abstraction for your code.