Swift Existentials (any) and Self Requirements Explained
Swift’s any keyword lets you store values behind a protocol type, while Self requirements limit which protocols can be used that way. This article explains what existential types are, why some protocols cannot be used as values, and how to recognize and fix the compiler errors that appear when protocol requirements depend on the conforming type itself.
Quick answer: Use any Protocol when you need a value of unknown concrete type that still conforms to a protocol. If a protocol mentions Self in a requirement, Swift usually cannot treat it as an existential because the protocol needs to know the exact conforming type.
Difficulty: Intermediate
You'll understand this better if you know: basic Swift protocols, generic functions, and the difference between a concrete type like String and a protocol like Collection.
1. What Are Existentials and Self Requirements?
An existential is a value that hides its concrete type behind a protocol interface. You use it when the exact type does not matter, only the behavior described by the protocol does.
- any marks an existential type explicitly.
- The stored value can be any concrete type that conforms to the protocol.
- You can call protocol requirements through the existential value.
- Some protocols cannot be used as existentials if they include Self requirements or associated types.
A Self requirement means the protocol refers to the conforming type itself. That creates a dependency on the exact concrete type, which often prevents Swift from erasing the type into a plain protocol value.
2. Why Existentials Matter
Existentials are useful whenever you want flexibility at runtime. They are common in APIs that collect unrelated conforming types into one container or pass around values where the concrete type is intentionally hidden.
They matter because they make protocols usable as values, not just as generic constraints. At the same time, they introduce tradeoffs: dynamic dispatch, less type information, and restrictions when protocols depend on Self.
3. Basic Syntax or Core Idea
The core syntax is simple. Add any before the protocol name when you want an existential value.
Minimal example
This example stores a value in a protocol-typed variable and calls a protocol method through it.
protocol Drawable {
func draw()
}
struct Circle: Drawable {
func draw() {
print("Drawing a circle")
}
}
let shape: any Drawable = Circle()
shape.draw()Here, shape can hold any type that conforms to Drawable. Swift knows it has a draw() method, but it does not keep the concrete type in the variable’s static type.
4. Step-by-Step Examples
Example 1: Storing different conforming values in one array
Existentials are handy when you want one collection to hold multiple concrete types that share the same protocol.
protocol Logger {
func log(_ message: String)
}
struct ConsoleLogger: Logger {
func log(_ message: String) {
print("Console: \(message)")
}
}
struct FileLogger: Logger {
func log(_ message: String) {
print("File: \(message)")
}
}
let loggers: [any Logger] = [ConsoleLogger(), FileLogger()]
for logger in loggers {
logger.log("App started")
}This works because both types conform to Logger. The array stores existential values, not a single shared concrete type.
Example 2: A protocol that cannot be used as an existential
Protocols with Self-based requirements often need the exact conforming type. That is where Swift draws a line.
protocol Clonable {
func clone() -> Self
}
struct Token: Clonable {
func clone() -> Token {
Token()
}
}
let value: any Clonable = Token()This is the kind of declaration that can fail or become unusable depending on the protocol’s exact requirements. If a protocol returns Self or compares Self values, Swift must preserve the concrete type relationship.
Example 3: Using an existential in a function parameter
You can accept an existential when your function only needs the protocol behavior and does not care about the concrete type.
protocol Greeter {
func greet() -> String
}
func welcome(guest: any Greeter) {
print(guest.greet())
}
struct Person: Greeter {
func greet() -> String {
"Hello"
}
}
welcome(guest: Person())This is useful when the function is a consumer of behavior, not a transformer of the concrete type.
Example 4: A generic function versus an existential parameter
Generics preserve the concrete type, while existentials hide it. That difference matters when you need to return or transform the same type.
protocol IdentifiableName {
var name: String { get }
}
struct User: IdentifiableName {
let name: String
}
func show<T: IdentifiableName>(item: T) -> T {
print(item.name)
return item
}
func showAny(item: any IdentifiableName) {
print(item.name)
}The generic version keeps the exact type and can return it. The existential version can use the interface, but it cannot preserve the original type in the same way.
5. Practical Use Cases
- Storing heterogeneous objects that share the same protocol, such as different formatters, loggers, or command handlers.
- Accepting values from APIs where the caller chooses the concrete type, but your function only needs protocol behavior.
- Building plugin-like systems where each module provides a different implementation of the same capability.
- Hiding implementation details while exposing a narrow API surface.
- Working with protocols whose requirements do not depend on Self or associated types.
6. Common Mistakes
Mistake 1: Forgetting to write any for a protocol value
Swift requires any when you mean an existential. Older code or tutorials may omit it, but modern Swift makes the distinction explicit.
Problem: The compiler may report that a protocol can only be used as a generic constraint, because the protocol is being written where a value type is expected.
protocol Shape {
func area() -> Double
}
let item: Shape = Circle()Fix: Mark the protocol type with any.
let item: any Shape = Circle()The corrected version works because Swift now knows you want an existential value.
Mistake 2: Expecting an existential to preserve the concrete type
An existential hides the real type, so you cannot use it the same way as a generic value.
Problem: Code that tries to return the exact stored type or use type-specific members will fail because the compiler only knows the protocol interface.
protocol Named {
var name: String { get }
}
func duplicate(value: any Named) -> any Named {
return value
}Fix: Use a generic function when you need the input and output to stay the same concrete type.
func duplicate<T: Named>(value: T) -> T {
return value
}The generic version preserves the concrete type, which is what the existential version cannot do.
Mistake 3: Using a protocol with Self requirements as a plain value type
Protocols that mention Self often cannot be turned into regular existential values. This is common with comparison, cloning, or chaining APIs.
Problem: Swift may produce an error such as “protocol can only be used as a generic constraint because it has Self or associated type requirements.” That means the protocol needs more type information than an existential can provide.
protocol Combining {
func combine(_ other: Self) -> Self
}
let value: any Combining = Example()Fix: Use a generic parameter so Swift can keep track of the exact conforming type.
func merge<T: Combining>(left: T, right: T) -> T {
return left.combine(right)
}The generic version succeeds because both parameters and the return value use the same concrete type.
7. Best Practices
Prefer generics when the concrete type must stay visible
If your function needs to preserve type identity, return the same type, or call requirements that depend on Self, a generic is usually better than an existential.
func render<T: Drawable>(value: T) {
value.draw()
}This keeps compile-time type information intact and avoids many existential limitations.
Use any when you really need heterogeneity
If a collection or property must hold different concrete types, an existential is the right tool.
let queue: [any Logger] = [ConsoleLogger(), FileLogger()]This is clearer than trying to force unrelated types into one generic container.
Keep protocol APIs existential-friendly when possible
If you design a protocol that should be easy to store or pass around as a value, avoid unnecessary Self requirements and associated types.
protocol PrintableTask {
var title: String { get }
func run()
}Designing with existential use in mind makes the protocol more flexible for callers.
8. Limitations and Edge Cases
- Existentials hide the concrete type, so you lose some compile-time optimizations and type relationships.
- Methods that return Self or take Self as an argument often block existential use.
- Protocols with associated types usually need generics or type erasure rather than a simple existential.
- Calling members on an existential can be limited to requirements that are part of the protocol interface.
- Protocol composition like any P & Q works only when the combined requirements are still existential-friendly.
A useful rule: if Swift must know the exact conforming type to check the API, the protocol is probably better used as a generic constraint than as an existential.
9. Practical Mini Project
Let’s build a tiny task runner that stores different task types in one list and executes them through a protocol existential.
protocol Task {
var title: String { get }
func run()
}
struct DownloadTask: Task {
let title = "Download file"
func run() {
print("Downloading...")
}
}
struct BackupTask: Task {
let title = "Backup database"
func run() {
print("Backing up...")
}
}
let tasks: [any Task] = [
DownloadTask(),
BackupTask()
]
for task in tasks {
print("Running: \(task.title)")
task.run()
}This project shows a common existential pattern: the runner only cares that each item can be titled and executed. The concrete task type stays hidden inside the array.
10. Key Points
- any creates an existential value from a protocol.
- Existentials are useful when you need a value that can hold multiple concrete conforming types.
- Self requirements often prevent a protocol from being used as a plain existential.
- Generics preserve concrete type information; existentials trade that away for flexibility.
- If a protocol needs the exact type to work correctly, prefer generics or redesign the protocol.
11. Practice Exercise
Create a protocol-based notification system that accepts different message senders in one array. Make sure each sender can provide a label and send a message.
- Define a protocol with two requirements: a read-only label and a send method.
- Create two or more concrete types that conform to the protocol.
- Store them in an array using any.
- Loop over the array and call both requirements.
Expected output: Each sender’s label and message should print in order.
Hint: Use only requirements that do not depend on Self, so the protocol stays existential-friendly.
Solution:
protocol Sender {
var label: String { get }
func send()
}
struct EmailSender: Sender {
let label = "Email"
func send() {
print("Sending email")
}
}
struct SMSSender: Sender {
let label = "SMS"
func send() {
print("Sending SMS")
}
}
let senders: [any Sender] = [EmailSender(), SMSSender()]
for sender in senders {
print(sender.label)
sender.send()
}This solution works because the protocol describes behavior that can be used through an existential without needing the concrete type itself.
12. Final Summary
Swift existentials let you treat a protocol as a value by writing any Protocol. That is useful when you need flexibility and do not care which concrete type is stored, as long as it conforms to the protocol.
Protocols with Self requirements are different. They need the exact conforming type to preserve type relationships, so they often cannot be used as simple existential values. In those cases, generics are usually the better fit, or the protocol may need to be redesigned to avoid Self-dependent requirements.
If you remember one rule, make it this: use any for protocol values that only need protocol behavior, and use generics when the exact concrete type matters. The more you practice switching between the two, the easier Swift’s type system becomes to read and design for.