Opaque Types (some) and Existential Types (any)
💡 The simplest way to begin
Existential types use
any Protocolto store values whose concrete type may vary.Opaque types use
some Protocolto hide one fixed concrete type from the caller.Boxed protocol types describe the container Swift uses to represent existential protocol values.
If you have ever wondered why Swift has both some and any, you are asking exactly the right question.
Most explanations begin by defining the keywords.
some Animal
any Animal
They explain that some creates an opaque type and any creates an existential type.
That is correct.
However, it does not immediately answer the more useful question:
Why does Swift need two different ways to hide a concrete type?
To answer that properly, we need to stop thinking about the keywords for a moment.
Instead, we need to ask how Swift can represent a value through a protocol.
Beginning With a Protocol
Suppose we have an Animal protocol.
protocol Animal {
var name: String { get }
func makeSound()
}
We can then create several concrete types that conform to it.
struct Dog: Animal {
let name: String
func makeSound() {
print("Woof!")
}
}
struct Cat: Animal {
let name: String
func makeSound() {
print("Meow!")
}
}
Dog and Cat both conform to Animal.
However, they remain different concrete types.
They may have different stored properties, different memory layouts and completely different implementations of the protocol requirements.
The Real Question
Imagine that we want to hide the concrete type and allow surrounding code to work only with Animal.
There are two very different situations in which we may want to do this.
Situation One
The concrete type may change.
A variable may contain a Dog now and a Cat later.
Situation Two
The concrete type never changes.
A function always returns a Dog, but we do not want callers to know that it is a Dog.
These are two different problems.
Swift therefore provides two different mechanisms.
The Two Mechanisms
At the implementation level, there are two fundamentally different ways Swift can represent a value through a protocol.
1. Boxed
Swift stores a concrete value inside a protocol container because the concrete type may vary.
any Animal
2. Opaque
Swift preserves one concrete type and hides its identity from the caller.
some Animal
Those mechanisms solve different problems.
Existential Types Use any
An existential type is written using the any keyword.
var animal: any Animal = Dog(name: "Milo")
The variable can store any concrete value that conforms to Animal.
It can therefore contain a Dog now and a Cat later.
var animal: any Animal = Dog(name: "Milo")
animal.makeSound()
animal = Cat(name: "Luna")
animal.makeSound()
The same variable stores values of two different concrete types over its lifetime.
This flexibility creates a problem.
Swift cannot give the variable the fixed memory representation of Dog because it may later contain a Cat.
It cannot give it the fixed representation of Cat either.
Swift therefore needs a common representation capable of holding different conforming types.
The Existential Container
Swift represents an existential protocol value using a container.
This is commonly described as an existential container or a box.
The container preserves enough information for Swift to store the value and call the correct protocol implementations at runtime.
Conceptually, it contains information such as:
- The underlying concrete value.
- The identity of the concrete type.
- The protocol conformance information needed to call the correct implementations.
The value inside the container is still a Dog or a Cat.
The concrete type has not ceased to exist.
It has simply been hidden behind the Animal interface.
Important
An existential value does not destroy the concrete type.
It hides the concrete type from the surrounding code while preserving enough runtime information for Swift to use the value correctly.
Why Is It Called an Existential Type?
This is where the terminology can begin to feel unnecessarily complicated.
We have already described the value as boxed.
Why do we also need the term existential?
The answer is that the two terms describe the same value from different viewpoints.
- Boxed protocol value describes how the value is represented.
- Existential type describes the language and type-system concept.
An existential value means:
There exists some concrete type that conforms to this protocol, and this container currently stores a value of that type.
Code using any Animal does not know which concrete Animal type exists inside the container.
It knows only that some conforming value exists.
This is the reason for the word existential.
Are Existential Types and Boxed Protocol Types the Same?
For practical Swift teaching, they are closely connected.
An any Animal value is an existential value, and Swift represents that value using an existential container.
However, the terms emphasize different things.
| Term | What It Describes |
|---|---|
| Existential Type | The type-system concept created using any Protocol. |
| Boxed Protocol Value | The runtime representation used to hold the hidden concrete value. |
It is therefore helpful to think of them as two descriptions of the same feature.
A useful mental model
any Animalcreates an existential value.That existential value is represented using a protocol container capable of holding different concrete Animal types.
Opaque Types Use some
An opaque type is written using the some keyword.
func favouritePet() -> some Animal {
Dog(name: "Milo")
}
The caller knows that the returned value conforms to Animal.
The caller does not know that the concrete type is Dog.
Swift does know.
More importantly, the concrete type is fixed.
This function always returns the same underlying concrete type.
Opaque Does Not Mean Unknown
The word opaque means that the caller cannot see through the abstraction to discover the concrete type.
It does not mean that Swift has forgotten the concrete type.
Swift continues to preserve its exact identity.
The compiler knows that the function returns Dog even though callers see only some Animal.
An opaque return type makes this promise:
I will always return one specific concrete type that conforms to this protocol.
I am simply choosing not to reveal which concrete type it is.
The Concrete Type Must Remain the Same
Every return path of an opaque-returning function must produce the same underlying concrete type.
This is valid:
func favouritePet() -> some Animal {
Dog(name: "Milo")
}
This is not valid:
func favouritePet(prefersCats: Bool) -> some Animal {
if prefersCats {
return Cat(name: "Luna")
}
return Dog(name: "Milo")
}
Dog and Cat both conform to Animal.
However, they are different concrete types.
The return type some Animal must refer to one fixed underlying concrete type.
Opaque Types Are Not Existential Types
An opaque type and an existential type both hide a concrete type, but they do not hide it in the same way.
An existential value hides the concrete type because the stored type may vary.
An opaque type hides the concrete type from the caller even though the type remains fixed.
| Existential Type | Opaque Type |
|---|---|
Written using any Protocol. |
Written using some Protocol. |
| The concrete type is hidden from code using the value. | The concrete type is hidden from the caller. |
| The concrete type may vary. | The concrete type remains fixed. |
| Uses an existential container. | Preserves one underlying concrete type. |
| Different conforming types can occupy the same variable. | Every value has the same underlying concrete type. |
| Designed for runtime flexibility. | Designed for abstraction while preserving type identity. |
The Most Important Question
The easiest way to decide between any and some is to ask:
Can the concrete type vary?
If the concrete type may vary, use an existential type.
var animal: any Animal = Dog(name: "Milo")
animal = Cat(name: "Luna")
If one concrete type must remain fixed but hidden, use an opaque type.
func favouritePet() -> some Animal {
Dog(name: "Milo")
}
Why Swift Needs Both Keywords
At first, it may appear that some and any perform the same job.
Both allow code to talk about a protocol without directly naming the concrete type.
However, they communicate opposite guarantees.
any Animal
This means:
This value contains some Animal-conforming type, and that concrete type may vary.
some Animal
This means:
This value has one particular Animal-conforming concrete type, and that type remains fixed even though you cannot see its identity.
Swift needs both keywords because these guarantees are not interchangeable.
Why the Terminology Feels Asymmetrical
The terminology can feel strange because we appear to have three terms for only two keywords:
- Boxed protocol type.
- Existential type.
- Opaque type.
The reason is that these terms do not all describe the same layer of the language.
Existential type is the language and type-system term associated with any.
Boxed protocol value describes the representation that allows that existential value to hold different concrete types.
Opaque type describes the separate language feature associated with some.
There are therefore two language features:
| Keyword | Language Feature |
|---|---|
any |
Existential type |
some |
Opaque type |
The additional word boxed describes how existential values can be represented, rather than introducing a third competing protocol feature.
A Better Mental Model
Protocols
│
▼
Protocol-based abstractions
│
├── Existential values
│ │
│ ├── Written using any
│ ├── Concrete type may vary
│ └── Stored using an existential container
│
└── Opaque values
│
├── Written using some
├── One concrete type is preserved
└── Concrete identity is hidden from the caller
This diagram separates the language feature from the implementation detail.
Existential and opaque types are the two language concepts.
Boxing explains how existential values can support different concrete types behind one protocol interface.
Existential Values Are About Choice at Runtime
An existential value gives the program flexibility to choose among different conforming types.
func adoptedAnimal(isCatPerson: Bool) -> any Animal {
if isCatPerson {
return Cat(name: "Luna")
}
return Dog(name: "Milo")
}
The function may return a Cat or a Dog.
The concrete type is chosen at runtime.
The caller receives an existential Animal value capable of representing either result.
Opaque Values Are About Hiding Implementation
An opaque return type is useful when an API wants to hide its implementation without giving up its fixed concrete type.
func adoptedAnimal() -> some Animal {
Dog(name: "Milo")
}
The function author remains free to change the implementation later.
The caller depends only upon the Animal interface.
However, within a particular implementation of the function, one underlying return type must remain consistent.
SwiftUI and some View
SwiftUI provides the most familiar example of opaque types.
var body: some View {
Text("Hello")
}
The concrete return type may be long and difficult to write explicitly.
Swift knows the complete type.
The developer using the property only needs to know that it conforms to View.
The type remains fixed even though its identity is hidden.
Collections and any
Existential types are useful when a collection must contain several different concrete types that conform to the same protocol.
let animals: [any Animal] = [
Dog(name: "Milo"),
Cat(name: "Luna")
]
Each element has the existential type any Animal.
One container holds a Dog and another holds a Cat.
The collection can treat both values uniformly through the Animal interface.
What Both Features Have in Common
Opaque types and existential types both allow code to depend upon a protocol instead of directly naming a concrete type.
Both expose the protocol requirements.
animal.name
animal.makeSound()
Neither automatically exposes members that exist only on the hidden concrete type.
animal.fetchStick() // Unavailable through Animal
That similarity is why the two features are often confused.
The difference is not primarily which methods can be called.
The difference is the guarantee Swift makes about the hidden concrete type.
The Core Difference
Existential types hide a concrete type because it may vary.
Opaque types hide a concrete type even though it remains fixed.
That distinction explains why Swift needs both any and some.
Interview Questions
What is an existential type?
An existential type is a type written using any Protocol that can store a value of any concrete type conforming to that protocol.
What is an opaque type?
An opaque type is a type written using some Protocol that hides one fixed concrete type from the caller.
Can the concrete type of an existential value change?
Yes.
An existential variable may store different conforming concrete types over time.
Can an opaque return type use different concrete types on different return paths?
No.
Every return path must produce the same underlying concrete type.
Is an opaque type the same as an existential type?
No.
An existential type allows the concrete type to vary, while an opaque type preserves one fixed concrete type.
Is an existential type the same as a boxed protocol type?
An existential is the language and type-system concept created using any.
The boxed protocol container is the representation that allows the existential value to hold a hidden concrete value.
Why does Swift need both some and any?
Swift needs both because one feature preserves a fixed hidden concrete type, while the other allows different hidden concrete types to be represented through one protocol interface.
What is the easiest way to remember the difference?
Ask whether the concrete type may vary.
If it may vary, think any.
If it remains fixed but hidden, think some.
Final Revision Note
Swift has two different protocol-based abstractions because it needs to express two different guarantees.
any Animal means that a value of some Animal-conforming concrete type exists inside an existential container, and that concrete type may vary.
some Animal means that one specific Animal-conforming concrete type has been preserved, but its identity has been hidden from the caller.
Remember
anymeans:"This could be any conforming concrete type."
somemeans:"This is one particular conforming concrete type, but I am not telling you which one."
