# Generics

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

> - **what**: A generic uses type parameters for types that aren't known yet, letting one function or type declaration serve many concrete types while preserving relationships between positions.
> - **trap**: An unconstrained type parameter has only the abilities shared by all types; replacing a generic with `Any`, adding arbitrary constraints, or confusing `some` with `any` changes the API contract.
> - **fix**: State which positions must have the same type, add the narrowest constraints for abilities the implementation actually uses, and let context drive type inference.

## What it is and why it exists

A generic declaration replaces one or more concrete types with named type parameters. When you call a generic function, its arguments and expected return type determine those parameters together. When you construct a generic type, the type arguments in angle brackets determine its storage and member signatures. `Array` and `Array` share one declaration but are different static types.

Generics don't mean “accept any value.” They mean “preserve a relationship without knowing the concrete type.” A function declared as `choose(_ left: T, _ right: T) -> T` says that both parameters and the result use one `T`. Replacing all three positions with `Any` removes that guarantee and makes the caller cast the result.

You meet generics throughout the Swift standard library in `Array`, `Dictionary<Key, Value>`, `Optional`, and `Result<Success, Failure>`. Application code also uses them for containers, transformations, data-access boundaries, and reusable algorithms. Code that serves one well-defined domain type doesn't need to become generic for appearance's sake.

A type parameter has no comparison, hashing, encoding, or domain-specific members on its own. When an implementation needs an ability, it declares a generic constraint, usually by requiring protocol conformance. The constraint lets the body use protocol members and rejects calls whose concrete types don't qualify.

A generic abstraction should preserve type information that matters to its callers. A parameter that is only logged or returned unchanged may need no constraint. If an algorithm makes business sense only for `Int`, a concrete type is more honest. The contract, not the number of angle brackets, determines whether the abstraction is useful.

## How it works

A generic function declares its parameter list after the function name, as in ``. The compiler collects requirements from every argument, explicit type annotation, and surrounding expression, then solves for a consistent concrete type. Swift doesn't support call-site syntax such as `identity(42)` to specialize a generic function explicitly. When inference needs help, annotate an argument or the receiving result.

A colon constraint expresses protocol conformance or class inheritance. `<Element: Comparable>` lets the body use the comparison operations guaranteed by `Comparable`, but it doesn't grant `Hashable` or `Codable`. You can write multiple requirements in the generic parameter list or move them into a `where` clause.

A `where` clause can constrain associated types and declare that two type expressions must be equal. `Left.Element == Right.Element` is a same-type requirement: the collection types may differ, but their elements must match. A generic declaration is available only when the compiler can prove every requirement at the call site.

A generic type includes its type parameters in its type identity. `Stack` has a `push` that accepts `String` and a `pop` that returns `String?`; the same members use `Int` on `Stack`. You can't change one `Stack` instance into a stack of integers after construction.

An associated type in a protocol is selected by the conforming type. Associated types and generic parameters both express type relationships, but the choice happens in different places: users supply a generic type's arguments, while conformers determine a protocol's associated types through their implementations. The compiler can usually infer an associated type from property and method signatures, so an explicit `typealias` isn't required.

Read the common forms by asking who declares the placeholder and who supplies its concrete type:

| Syntax | Relationship | Source of the concrete type |
| --- | --- | --- |
| `func load(_: T)` | One named type parameter | Inferred at the call site |
| `struct Box` | Part of a generic type's identity | Supplied when naming the type |
| `T: Hashable` | `T` must satisfy a protocol | Conformance checked by the compiler |
| `where A.Item == B.Item` | Two type expressions are equal | Both proved at the call site |
| `associatedtype Item` | A protocol leaves a type slot | Selected by the conforming type |

Type inference isn't dynamic typing. Every expression still has a definite static type after compilation; inference merely avoids repeating it in source. When an error becomes long, one meaningful annotation at the call boundary usually exposes the conflict more clearly than a pile of conversions inside the implementation.

### Inference collects information in both directions

Type inference doesn't flow only from arguments to results. An assignment target, closure parameter and result types, overload candidates, and literal defaults all join the same constraint-solving process. In `let ids: Set = []`, the left side determines the empty literal's type; `let ids = []` doesn't provide enough information.

When inspecting one generic call, mark these four sources of information in order:

- The static type of each argument.
- Any expected type declared where the result is received.
- Operations the closure body applies to its parameters and result.
- Requirements introduced by the generic declaration, `where` clause, and selected overload.

Those facts must form one consistent solution. If two arguments imply incompatible choices for `T`, the compiler doesn't pick one and doesn't insert a numeric conversion. Decide which type the domain requires, then perform a named conversion at the boundary.

### Constraints belong on the narrowest capability boundary

Constraints can appear on a generic declaration, method, constrained extension, or conditional conformance. Their position determines whether the whole API or one capability is restricted. Moving a requirement outward affects more unrelated members.

| Location | Requirement it should express | Effect on other members |
| --- | --- | --- |
| `struct Cache<Key: Hashable, Value>` | Storage always needs hashable keys | Restricts every instance |
| `func contains(...) where Element: Equatable` | Only this operation needs equality | Leaves other methods available |
| `extension Box where Value: Codable` | A group of encoding members shares a requirement | Gates only that extension's members |
| `extension Box: Equatable where Value: Equatable` | Conformance is conditional | Doesn't block construction of the base type |

The narrowest location isn't always a method. If a type invariant depends on dictionary keys, `Key: Hashable` belongs on the type. If only a diagnostic export needs encoding, `Codable` shouldn't infect the core declaration. Start at the expression that uses the ability and move outward to the first stable API boundary.

## Examples

These four standalone programs progress from generic functions to generic types, `where` constraints, and associated types. This environment has no local Swift toolchain, so each block carries the required not-executed marker. Each displayed output was separately checked with a Swift 6.3.3 compiler.

### Inferring type parameters at the call site

The two parameters and result of `earlier` share `Value`. Its `Comparable` constraint makes `<` available, while `repeated` doesn't inspect the value and needs no extra constraint.

<!-- quick -->

```swift
// file: inferred_types.swift
// # not executed here: Swift toolchain is not installed.
func earlier<Value: Comparable>(_ first: Value, _ second: Value) -> Value {
    first < second ? first : second
}

func repeated<Value>(_ value: Value, count: Int) -> [Value] {
    precondition(count >= 0)
    return Array(repeating: value, count: count)
}

let earlierNumber = earlier(42, 17)
let earlierWord = earlier("pear", "apple")
let stages = repeated("ready", count: 3)

print(earlierNumber)
print(earlierWord)
print(stages.joined(separator: ","))
```

```text
17
apple
ready,ready,ready
```

<!-- /quick -->

The first call infers `Value` as `Int`, and the second infers `String`. Both arguments in any one call still need the same type. A generic function doesn't automatically combine `Int` and `Double` into a shared numeric type.

The result also participates in inference. When the arguments aren't enough, write `let result: DesiredType = ...` or first construct an input with a known type. Don't let generated code fake an unprovable relationship with a forced cast.

The `precondition` describes the valid range of `count`, which is separate from a generic constraint. Generic constraints check type abilities; runtime preconditions check a particular value. Neither can replace the other.

### Storing one element type in a generic type

`Stack` uses the same `Element` for storage, `push`, and `pop`. Its `map` method introduces a separate `Output`, so the transformed result can be a different stack type.

```swift
// file: generic_stack.swift
// # not executed here: Swift toolchain is not installed.
struct Stack<Element> {
    private var storage: [Element] = []

    var count: Int { storage.count }

    mutating func push(_ element: Element) {
        storage.append(element)
    }

    mutating func pop() -> Element? {
        storage.popLast()
    }

    func map<Output>(_ transform: (Element) -> Output) -> Stack<Output> {
        var result = Stack<Output>()
        for element in storage {
            result.push(transform(element))
        }
        return result
    }

    func values() -> [Element] { storage }
}

var jobs = Stack<String>()
jobs.push("build")
jobs.push("deploy")
let lengths = jobs.map { $0.count }

print(jobs.pop() ?? "none")
print(lengths.values())
```

```text
deploy
[5, 6]
```

The type of `jobs` is fixed as `Stack`, so `push(3)` fails at compile time. `map` doesn't turn the original stack into an integer stack. It creates a new `Stack`, and that result type preserves the relationship across the transformation.

`pop` returns an optional because an empty stack is a valid state. Generics don't decide boundary behavior for an API. `Element?` describes absence more accurately than a force unwrap or a sentinel value that couldn't work for every possible `Element`.

This implementation deliberately keeps the interface small. If you add search, require `Element: Equatable` only on the constrained extension that provides that operation. Don't make the whole `Stack` reject non-equatable elements.

### Describing cross-type relationships with where

`sameElements` accepts two different collection types but requires their element types to match and support equality. `unique` only needs hashable sequence elements and removes duplicates while preserving first-seen order.

```swift
// file: where_constraints.swift
// # not executed here: Swift toolchain is not installed.
func sameElements<Left: Collection, Right: Collection>(
    _ left: Left,
    _ right: Right
) -> Bool where Left.Element == Right.Element, Left.Element: Equatable {
    left.elementsEqual(right)
}

func unique<Values: Sequence>(_ values: Values) -> [Values.Element]
where Values.Element: Hashable {
    var seen: Set<Values.Element> = []
    return values.filter { seen.insert($0).inserted }
}

let states = ["queued", "running", "done"]
let activeStates = states[0...1]

print(sameElements(["queued", "running"], activeStates))
print(sameElements(["running", "queued"], activeStates))
print(unique(["A", "B", "A", "C", "B"]))
```

```text
true
false
["A", "B", "C"]
```

The left side is `Array` and the right is `ArraySlice`, so the collection types needn't match. `Left.Element == Right.Element` proves that `elementsEqual` compares one element type, while `Equatable` supplies equality.

`unique` uses a `Set` to track elements it has seen, which requires `Hashable`. The requirement belongs to this function, not to unrelated operations on the input type. The returned array keeps first-seen order and doesn't rely on a set's iteration order.

Constraints are part of the public API. If a later implementation no longer needs hashing, consider removing `Hashable`. Leaving a stale constraint excludes types for which the function would otherwise work correctly.

### Letting a conformer select an associated type

`Catalog` declares `Item` as a primary associated type. The angle brackets make it possible to constrain that type with `Catalog`, but they don't turn the protocol itself into a generic type like a `Catalog` structure would be.

```swift
// file: associated_types.swift
// # not executed here: Swift toolchain is not installed.
struct Product {
    let id: Int
    let name: String
}

protocol Catalog<Item> {
    associatedtype Item
    var items: [Item] { get }
}

struct MemoryCatalog<Item>: Catalog {
    let items: [Item]
}

func first<C: Catalog>(in catalog: C) -> C.Item? {
    catalog.items.first
}

func names(in catalog: some Catalog<Product>) -> [String] {
    catalog.items.map(\.name)
}

let products = MemoryCatalog(items: [
    Product(id: 1, name: "Keyboard"),
    Product(id: 2, name: "Mouse")
])

if let product = first(in: products) {
    print("First: \(product.name)")
}
print(names(in: products).joined(separator: ", "))
```

```text
First: Keyboard
Keyboard, Mouse
```

`MemoryCatalog` lets the compiler infer `Catalog.Item == Product` from its `items` property. `first` preserves the associated type of any conformer and returns `C.Item?`; it doesn't degrade the result to `Any?`.

In parameter position, `some Catalog` is a generic parameter that doesn't need a source-level name. Each call still passes one concrete conforming type, and the body knows that its `Item` is `Product`. If two parameters must share exactly the same catalog type, name the type parameter and use it twice.

A primary associated type name must correspond to an associated type declared by the protocol. Its main purpose is concise constraints such as `some Catalog` or `any Catalog`. It doesn't mean callers fill an ordinary generic argument on the protocol declaration.

## Pitfalls

### Supplying explicit type arguments to a function call

> **Pitfall:** Generated code often writes `identity(42)`, borrowing explicit specialization syntax from other languages. Swift rejects this call syntax even when `identity` declares `T`.

**Fix:** let the arguments and expected result drive inference, as in `let value: Int = identity(42)`. If ambiguity remains, annotate the data boundary instead of covering the problem with `as!`.

### Replacing a type parameter with Any

> **Pitfall:** A signature such as `[Any] -> Any` appears to accept more input, but it loses the relationship between the elements and result. The function body is reduced to casts or runtime checks, and callers can't learn the result type from the signature.

**Fix:** use a named type parameter when a relationship exists, such as `[Element] -> Element?`. Choose `Any` or an existential only when heterogeneous values are part of the data model and every runtime branch has defined behavior.

### Applying a constraint too broadly

> **Pitfall:** To make one `contains` method compile, a model may constrain an entire container with `Element: Equatable & Hashable & Codable`. Callers that only need `count` must then satisfy abilities irrelevant to their operation.

**Fix:** put each requirement on the method, extension, or conditional conformance that uses it, and remove protocols the implementation doesn't use. Keep one compile test for a supported type and one for a type that should be rejected.

### Expecting one type parameter to hold heterogeneous elements

> **Pitfall:** `[T]` has one `T` in any concrete use. A generic array can't automatically hold both `Circle` and `Rectangle` merely because both conform to `Shape`.

**Fix:** use `T: Shape` when you need one concrete type and its static relationships. Use `[any Shape]` or an appropriate enum when one collection genuinely stores different conformers at runtime. That choice affects available members and type identity, not just spelling.

### Treating some and any as interchangeable

> **Pitfall:** `some Protocol` preserves one hidden concrete type identity, while `any Protocol` stores a conforming value whose concrete type may vary at runtime. A mechanical replacement can break same-type relationships or make members that depend on associated types unavailable.

**Fix:** first state who chooses the concrete type and whether it may vary between calls. Use a generic parameter when the caller supplies one concrete type, `some` when the implementation hides one result type, and `any` for genuine runtime heterogeneity.

<!-- deep -->

## Who selects the concrete type

Generic parameters, `some`, and `any` all let source code depend on protocol abilities, but they preserve different information. The useful questions are who selects the concrete type and whether a same-type relationship must cross the API boundary.

| Form | Who selects the concrete type | Preserved relationship |
| --- | --- | --- |
| `<T: P>` | Each call site | Every `T` position is the same |
| Parameter-position `some P` | Each call site | The parameter has one unnamed concrete type |
| Result-position `some P` | The function implementation | Every result has the same underlying type |
| `any P` | The runtime value | Only conformance to `P` is guaranteed |

Two separate parameter-position occurrences of `some P` introduce two unnamed type parameters, so they needn't match. If the implementation needs to swap, compare, or pass values between them, declare `<T: P>` and use `T` in both places. Omitting the name works only when the body doesn't need to refer to that type again.

An opaque result type is selected by the implementation and hidden from callers, but it still represents one fixed underlying type. An existential permits each value to contain a different conformer, which suits heterogeneous storage and runtime replacement. The latter introduces indirection when needed, but that doesn't justify a fixed performance multiplier. Measure the real call path.

## Associated types form dependent members

A protocol's associated type depends on its conforming `Self`. Given `C: Catalog`, `C.Item` is a dependent member type. The compiler knows `Item` only after it determines `C` or proves another constraint. That is how a generic function connects an input catalog to its output element.

A same-type requirement can connect several dependent members, such as `Left.Item == Right.Item`. The conforming types may still differ; only the named members are equal. Replacing that requirement with `Left == Right` narrows the contract to one catalog type and discards interoperability between different implementations.

Primary associated types only provide lightweight constraint syntax. `Catalog` constrains the primary associated type `Item` to `Product`; every name in the angle brackets must be listed by the protocol and correspond to an associated type declaration. The conformer still has to satisfy the relationship.

When an existential is opened, associated-type information may be usable only within a local scope. If a function needs to pass one value's associated type into another parameter or expose it in a result, a named generic parameter usually states the relationship more clearly. Don't erase a type first and try to reconstruct the compiler's lost proof with a forced cast.

## Specialization isn't a source-level contract

The Swift compiler may specialize generic code for concrete types, or it may share an implementation when doing so preserves observable behavior. Optimization level, module boundaries, visibility, and compiler version all affect that choice. Source code can't assume that every type gets a distinct machine-code copy, and it can't derive fixed performance numbers from that assumption.

The primary benefits of generics are static type relationships and reuse. Performance questions require a release build, the target platform, realistic data, and observation of the actual call path. Without measurements, you can describe possible indirection, boxing, or code-size tradeoffs, but not claim that a cost occurred.

Constraints can affect optimization opportunities, but more constraints don't imply faster code. An unnecessary constraint first changes who may call the API, and it can spread into declarations above it. Write the smallest correct contract, find the bottleneck with profiling, and only then consider a concrete overload or a changed boundary for a hot path.

## Overloads and inference need clear boundaries

The overload set also participates in generic constraint solving. When two overloads accept the same argument, the compiler compares which is more specific; complex closures, default arguments, and result context can make that choice hard to anticipate. A public API shouldn't require readers to guess subtle overload ranking.

If two generic overloads express different business meanings, distinct base names are usually clearer. If one only supplies an optimized path for a more specific type, its behavior and failure rules must match the general version. Call tests should cover values with identical runtime data but different static types.

When an error occurs in a long chain, assign each stage to a typed local constant. This identifies the point that lacks context and prevents an upstream edit from silently choosing another overload. Those names are often worth keeping after diagnosis because they explain the meaning of each transformation.

## Keep generic boundaries readable

When a public signature exposes many type parameters, nested associated types, and same-type requirements, callers struggle to see the actual invariant. First ask whether an existing standard-library protocol describes the input, then give business relationships meaningful names. A type alias shortens repeated spelling but doesn't reduce the underlying complexity.

When compiler diagnostics become hard to read, split a long expression into typed intermediate values or move requirements into a named helper. These changes give both the constraint solver and the reader local boundaries. Adding `Any`, `as!`, or unrelated protocols merely moves the problem to runtime or to a higher-level API.

Library evolution also distinguishes semantic constraints from implementation constraints. If a public function declares `Element: Hashable`, callers may treat that as part of the contract even if a future body no longer hashes anything. Before publishing a requirement, answer why the caller needs to know about it.

Don't test a generic API only with one happy-path `Int`. Pick two structurally different types that both satisfy the constraints, showing that behavior comes from the protocol. Keep a separate example that doesn't compile to prove what the boundary rejects. Runtime tests verify behavior; compile tests verify the type contract.

<!-- /deep -->

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

## Further reading

- [Swift Language Guide: Generics](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/generics/)
- [Swift Language Guide: Protocols](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/protocols/)
- [Swift 6.3 release notes](https://www.swift.org/blog/swift-6.3-released/)
