# Protocols

Source: https://codewiki.com/swift/protocols/

> - **what**: A protocol declares a set of capability requirements. A structure, enumeration, or class conforms by supplying matching implementations, so callers can depend on a contract instead of a concrete type.
> - **trap**: A method added only by a protocol extension doesn't become a dynamically replaceable witness. Replacing a generic boundary with `any Protocol` also erases type relationships that callers may need.
> - **fix**: Put dynamically dispatched members in the protocol declaration, then choose generics, `some`, or `any` according to ownership: the caller chooses the type, the implementation chooses one type, or storage accepts any conformer at runtime.

## What it is and why it exists

A Swift protocol is a capability contract. It can require instance or type properties, methods, initializers, and subscripts without prescribing whether a conformer uses stored properties, computed properties, or another internal design. Structures, enumerations, and classes can all declare protocol conformance, so abstraction doesn't depend on class inheritance.

Conformance is a named, explicit relationship rather than structural matching by member shape. Even if a type happens to have every same-named member, it must state the protocol name in its declaration or an extension before it can be used as a conformer.

The explicit declaration gives the compiler one place to check and lets readers search for types that promise the contract. A type whose members merely share names but have different semantics isn't silently accepted.

Protocols answer “what can the caller do?” instead of “where does this object sit in an inheritance hierarchy?” A sending function that only needs a value to produce a message doesn't need to know whether the value is a structure, actor, or test double. This narrows an API boundary and lets the compiler check the contract when an implementation is replaced.

You encounter protocols throughout the standard library in `Sequence`, `Collection`, `Equatable`, `Hashable`, `Codable`, and `Sendable`. Application code often uses them to isolate storage, networking, clocks, and logging, or to give several value types the same algorithm entry point.

A protocol isn't an automatic switch for reuse or decoupling. An oversized protocol forces conformers and test doubles to implement unrelated capabilities; a tiny protocol with no stable consumer adds names and navigation. Start with the operations a caller actually needs, then decide whether that boundary deserves a protocol.

## How it works

Each property, method, initializer, or subscript in a protocol declaration is a protocol requirement. A conforming type must provide a visible, type-matching implementation or use a default supplied by a protocol extension. The compiler checks completeness where conformance is declared instead of waiting for a missing member at runtime.

A property requirement describes read or read-write capability only. `{ get }` can be satisfied by a constant stored property, variable stored property, or computed property; `{ get set }` can't be satisfied by a read-only implementation. A type-property requirement is written with `static` in the protocol, and a class can provide the matching member using an allowed implementation form.

When a value-type method needs to change `self` or an instance property, the protocol requirement must be marked `mutating`. Structure and enumeration implementations also use `mutating`, while class implementations don't. When a protocol requires an initializer, a non-`final` class generally uses `required` so subclasses still provide that construction entry point.

### Inheritance and class-only protocols

A protocol can inherit multiple protocols, collecting their requirements into a new long-term contract. A type conforming to the child protocol must satisfy the whole inheritance chain. This isn't implementation inheritance: the protocol still stores no instance state and supplies no superclass object.

Adding `AnyObject` to the inheritance list restricts conformance to classes. Use that restriction only when the contract depends on reference semantics, weak references, or object identity. Adding `AnyObject` mechanically to imitate a class interface needlessly excludes structures and enumerations.

A class can also appear in a protocol inheritance list as a superclass requirement, making every conformer part of that class hierarchy. This is stronger than `AnyObject` and fits only a boundary whose default implementation must call behavior from that superclass.

### Default implementations and dispatch

A protocol extension can implement requirements already declared by the protocol, and it can add convenience members. Those cases dispatch differently: an implementation of a requirement participates in conformance, while an extension-only member is selected from the static type visible at the call site and doesn't become a replaceable protocol witness.

Putting an algorithm in an extension works well when it is valid for every conformer. If conformers should be able to change behavior observed through a protocol value, declare the member in the protocol first and provide its default in an extension. Merely writing a same-named concrete member doesn't turn the extension member into a requirement.

### Three abstraction boundaries

A protocol name can appear in several type contexts, but those contexts aren't equivalent. A generic constraint lets the caller choose a concrete type; the opaque type `some P` lets an implementation choose one hidden concrete type; the existential type `any P` uses a boxed value to hold some conforming type at runtime.

| Spelling | Who chooses the concrete type | Relationship retained | Typical use |
| --- | --- | --- | --- |
| `<T: P>` or parameter `some P` | Caller | `T` stays one type within the call | Algorithm parameters, static type relationships |
| Return `some P` | Implementation | Hidden but fixed underlying type | Hide a factory or composed result |
| `any P` | Runtime assigner | Only the current value's conformance to `P` | Heterogeneous collections, replaceable storage slots |

The flexibility of `any P` comes from type erasure and a level of indirection when needed. An existential exposes only members guaranteed by the protocol; concrete-type APIs require a runtime cast. Don't replace every generic parameter with `any` just because the signature looks shorter, because same-type relationships between associated types may disappear.

### Associated types and composition

An associated type is a type placeholder declared by a protocol and fixed by each conforming type. It expresses relationships such as “a container has one element type” or “a parser produces one output type.” A generic function can then relate `Store.Value` to a parameter or return value.

A primary associated type is named in angle brackets after the protocol name, but it must still be declared with `associatedtype` in the body. It lets `some Feed` and `any Feed` constrain the associated type concisely; it doesn't turn the protocol into an ordinary generic type.

A protocol composition `P & Q` requires one value to satisfy both contracts, but it doesn't declare a new protocol. Declare a real protocol when the combination needs a name, inheritance, or long-term evolution. Use `P & Q` when a local parameter simply needs the intersection of two capabilities.

## Examples

These three standalone programs progress from basic requirements and defaults to extension-member dispatch and associated types across `some` and `any`. This environment has no Swift toolchain, so each block carries the required not-executed marker; the shown output is the expected result of these deterministic examples.

### Defining a minimal capability

`Resettable` requires only a summary and a reset operation; recording a query remains a capability of `SearchHistory`. The extension derives `isEmpty` from the protocol requirement so every conformer can reuse that logic.

<!-- quick -->

```swift
// file: search_history.swift
// # not executed here: Swift toolchain is not installed.
protocol Resettable {
    var summary: String { get }
    mutating func reset()
}

extension Resettable {
    var isEmpty: Bool { summary.isEmpty }
}

struct SearchHistory: Resettable {
    private var queries: [String] = []

    var summary: String { queries.joined(separator: ",") }

    mutating func record(_ query: String) {
        queries.append(query)
    }

    mutating func reset() {
        queries.removeAll()
    }
}

var history = SearchHistory()
history.record("swift")
history.record("protocols")
print(history.summary)
print(history.isEmpty)
history.reset()
print(history.isEmpty)
```

```text
swift,protocols
false
true
```


<!-- /quick -->

`mutating` is part of the protocol contract; without it, a value-type implementation can't change itself. The `history` binding must also be declared with `var` before code can call a potentially mutating requirement. A class conformer implements the same requirement without writing `mutating`.

The extension's `isEmpty` reads only `summary`, so it doesn't know how the query array is stored. Another conformer could calculate its summary from a database count or remote state as long as it satisfies the same public contract.

### Observing static selection for extension members

`headline()` is a protocol requirement, while `category()` exists only in the extension. The concrete value sees `Incident.category()`, but the existential finds the extension version through its static interface.

```swift
// file: dispatch_boundary.swift
// # not executed here: Swift toolchain is not installed.
protocol Reportable {
    func headline() -> String
}

extension Reportable {
    func headline() -> String { "Untitled" }
    func category() -> String { "general" }
}

struct Incident: Reportable {
    func headline() -> String { "Disk full" }
    func category() -> String { "operations" }
}

let incident = Incident()
let report: any Reportable = incident

print(incident.headline())
print(report.headline())
print(incident.category())
print(report.category())
```

```text
Disk full
Disk full
operations
general
```

`headline()` selects the `Incident` implementation through conformance, so concrete and existential calls agree. `category()` has no corresponding requirement witness; when the variable's static type is `any Reportable`, the compiler selects the protocol-extension implementation.

If category is meant to be replaceable behavior, add `func category() -> String` to `Reportable` and keep the default in the extension. The concrete implementation then applies to every call through the protocol boundary, not only calls made directly through the concrete type.

### Preserving or erasing an associated type

`Feed` names `Item` as its primary associated type. The factory uses `some Feed` to hide its concrete implementation while preserving the element type. The array uses `any Feed` to hold different conformers while limiting its interface to protocol-guaranteed operations.

```swift
// file: feed_boundaries.swift
// # not executed here: Swift toolchain is not installed.
protocol Feed<Item> {
    associatedtype Item
    func next() -> Item?
}

struct Single<Value>: Feed {
    let value: Value
    func next() -> Value? { value }
}

struct Empty<Value>: Feed {
    func next() -> Value? { nil }
}

func releaseFeed() -> some Feed<String> {
    Single(value: "release")
}

let feeds: [any Feed<String>] = [
    releaseFeed(),
    Empty<String>()
]

for feed in feeds {
    print(feed.next() ?? "none")
}
```

```text
release
none
```

A function returning `some Feed` must choose the same underlying type on every return path. The caller can't name that type, but it knows `Item == String`; this preserves more information than an unconstrained protocol boundary.

The array needs to hold both `Single` and `Empty`, so it uses `any Feed`. If every element already has one concrete type, a generic collection preserves more static relationships and is usually easier to feed into further generic algorithms.

### Composing the capabilities needed locally

When a function needs a name and a priority, it can spell a protocol composition directly instead of inventing a protocol for a one-off intersection. An existential array can hold several concrete types that satisfy both contracts.

```swift
// file: protocol_composition.swift
// # not executed here: Swift toolchain is not installed.
protocol Named {
    var name: String { get }
}

protocol Prioritized {
    var priority: Int { get }
}

struct BuildJob: Named, Prioritized {
    let name: String
    let priority: Int
}

struct SupportTicket: Named, Prioritized {
    let name: String
    let priority: Int
}

let queue: [any Named & Prioritized] = [
    BuildJob(name: "compile", priority: 2),
    SupportTicket(name: "login", priority: 1)
]

for item in queue.sorted(by: { $0.priority < $1.priority }) {
    print("\(item.priority):\(item.name)")
}
```

```text
1:login
2:compile
```

`Named & Prioritized` describes only the capability intersection at this storage location; it doesn't create a new name that other protocols can inherit. If the queue contract later gains cancellation, retry, or concurrency-safety requirements, declare a named protocol and evolve it in one place.

This array chooses `any` because the two elements have different concrete types. If a function processes one caller-provided value at a time, a generic constraint `<Item: Named & Prioritized>` preserves that call's concrete type.

## Pitfalls

### Treating extension members as overridable requirements

> **Pitfall:** A protocol extension adds `render()`, and a concrete type declares a method with the same name, which looks like an override. Once the value is stored as `any Protocol`, the call can return to the extension implementation.

**Fix:** members that need polymorphic replacement must appear in the protocol declaration first. Let the extension provide only the default witness, and test calls through both the concrete and existential types.

### Replacing every generic with `any`

> **Pitfall:** Generated code often shortens `<T: P>` to `any P` while losing facts such as two arguments sharing a type, a result depending on its input, or an associated type being fixed.

**Fix:** write down the same-type relationships callers need to retain. Choose `any` only when a storage slot must change concrete type at runtime or a collection truly needs heterogeneous elements; otherwise consider a generic or `some` boundary.

### Misreading `{ get }` and `{ get set }`

> **Pitfall:** `{ get }` promises only that callers can read; it doesn't require an immutable implementation. `{ get set }` promises writes through the protocol interface. Changing the former to the latter excludes otherwise valid read-only computed properties.

**Fix:** define the smallest access capability the consumer needs. Don't widen the protocol because one current implementation happens to use `var`; require a setter only when protocol consumers must assign through it.

### Assuming an existential conforms to its protocol

> **Pitfall:** `any P` can hold a value that conforms to `P`, but that doesn't mean the box itself conforms to `P` in every generic context. The difference becomes visible around `Self`, associated-type inputs, and nested collections.

**Fix:** identify the `Self` and associated-type relationships required by the call. Let the compiler open an existential for a direct call when possible; when relationships must persist across values, use a generic parameter, constrain a primary associated type, or design an explicit type-erasing wrapper.

### Letting a default hide an omission

> **Pitfall:** A default lets a new conformance compile, but it can silently apply generic behavior where a domain type should explicitly decide authorization, retries, or persistence.

**Fix:** use defaults only for semantics that are correct for every conformer. Require explicit implementations for security- or business-critical behavior, or test that each conformer deliberately accepts the default.

<!-- deep -->

## Conformance witnesses and call selection

When the compiler accepts a conformance, it maps each requirement to a concrete implementation, commonly called a witness. A call through a protocol boundary can use conformance information to find the right implementation. The language guarantees observable dispatch semantics; an application shouldn't treat one compiler version's table layout as ABI.

A default implementation of a requirement from a protocol extension can also be a witness. If the concrete type provides its own matching implementation when conformance is declared, that implementation is selected. The important boundary is still whether the member appears in the protocol declaration, not whether its implementation happens to live in the type body or another extension file.

An extension-only member has no witness slot. The compiler resolves it using the expression's static type: a same-named concrete member can win when the static type is concrete, while the extension member is visible when the static interface exposes only the protocol. Saying “protocol methods use dynamic dispatch” without this qualification creates the wrong prediction.

A generic function still carries conformance information, and the optimizer may specialize or inline code for a concrete type, but source semantics don't promise a particular machine-code layout. Measure performance in a release build on the target platform. Choosing a generic or existential boundary is first a decision about API type relationships and runtime flexibility.

## Existentials and associated types

An existential packages a current concrete value with its conformance information inside an abstract container. The variable can later accept another conforming type, so concrete type identity isn't part of the static interface. Callers can rely only on protocol requirements and primary-associated-type constraints retained explicitly in the signature.

Older material often says that “a protocol with associated types can't be used as a type.” Modern Swift supports `any P` and can implicitly open an existential in many calls. The actual limit is that erasure may leave too little information to establish a required relationship. If a method accepts `Item`, for example, an unconstrained `any Feed` doesn't tell the caller which concrete `Item` it can pass.

`any Feed` retains `Item == String`, so operations that read `String?` have a known type. If two separate existentials must share the same unknown `Item`, boxing them independently still doesn't prove that relationship. Lifting their common type into a generic parameter lets the compiler check the invariant across values.

A handwritten type-erasing wrapper can store required operations in closures and retain an associated type in the wrapper's generic parameter. It is useful when an API needs a stable named type, extra state, or compatibility with an older boundary, but it adds forwarding code. Don't generate an `AnyP` wrapper mechanically when modern `any` already satisfies the requirement.

## The fixed identity of opaque types

Return-position `some P` hides an underlying type name, but each declaration still chooses one fixed underlying type. A function can't return unrelated `A` and `B` types from separate branches merely because both conform to `P`. Return `any P` or unify the choices with an enumeration when runtime switching is required.

An opaque type retains underlying type identity, so the compiler can preserve associated-type and `Self` relationships. A caller can't spell the hidden name, but it can rely on constraints exposed by the declaration. `some Collection` hides the concrete collection while publishing `Element == String`.

Parameter-position `some P` is shorthand for a generic parameter, so each call chooses the concrete type. Return-position `some P` gives that choice to the implementation. The same keyword reverses ownership in the two positions, and a generated signature review must make that explicit.

## Conditional conformance and composition boundaries

A generic type can conform to a protocol only when its type parameter meets a condition; this is conditional conformance. If a wrapper can compare contents only when `Value: Equatable`, put that condition on the conformance extension instead of promising an ability that some instances can't provide.

```swift
// file: conditional_conformance.swift
// # not executed here: Swift toolchain is not installed.
struct Box<Value> {
    let value: Value
}

extension Box: Equatable where Value: Equatable {}

print(Box(value: 3) == Box(value: 3))
```

```text
true
```

This declaration gives `Box` `Equatable` conformance without pretending that `Box` is comparable. The condition belongs to the conformance itself, so consumers can keep using generic constraints and let the compiler prove whether the capability exists.

Adding conformance between a type and protocol that both come from other modules is retroactive conformance. Another module can declare the same pairing, creating a conflict when both reach one program. Prefer a local wrapper that owns the conformance; when retroactive conformance is necessary, treat global uniqueness as an integration contract rather than merely making the current file compile.

Don't confuse protocol inheritance with protocol composition either. `protocol CachedStore: Store, Sendable` declares a named long-term contract that other protocols can inherit. A parameter `any Store & Sendable` asks for the intersection only at that position. Where the contract is owned and evolved determines which form fits.

## Testing a protocol boundary

A protocol test shouldn't verify only one concrete implementation. Prepare at least two conformers with different behavior to expose misuse of defaults, concrete-type leakage, and disagreement between a test double and production implementation. If there is only one implementation, first confirm that the protocol creates a valuable replacement boundary.

A dispatch test should call the same value first through its concrete type and then through `any P`. If the paths disagree, check whether a member was omitted from the requirements. Associated-type tests should cover both allowed same-type combinations and different-type combinations that compilation is supposed to reject.

An existential collection also needs a type-switching test. Insert two conforming types and operate on each only through the protocol interface, confirming that code doesn't rely on a hidden concrete type. If the operation fills with forced casts, the protocol may not express the truly shared capability.

<!-- /deep -->

[Checkpoint: swift/protocols](https://codewiki.com/swift/protocols/#checkpoint)

## Further reading

- [Swift language guide: Protocols](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/protocols/)
- [Swift language guide: Opaque and boxed protocol types](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/opaquetypes/)
- [Swift Evolution SE-0335: Existential `any`](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0335-existential-any.md)
- [Swift Evolution SE-0346: Lightweight same-type requirements for primary associated types](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0346-light-weight-same-type-syntax.md)
- [Swift 6.3.3 release](https://github.com/swiftlang/swift/releases/tag/swift-6.3.3-RELEASE)
