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.

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

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

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

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.

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.