When using Swift protocols, you eventually hit a wall: errors about associated types when you try to use Equatable as a variable type or store Collection in a property. The once-infamous “Protocol can only be used as a generic constraint because it has Self or associated type requirements” is a prime example. The identity of this wall is associatedtype.
This is part 4 of the intermediate series. We cover what associated types are, why that error occurred, and the language evolution leading to primary associated types. If you have read the generics and some·any articles, you already have all the necessary ingredients.
What Are Associated Types — Type Placeholders Built into Protocols
In the generics article, <T> was described as a “placeholder to fill in a type later.” An associated type is that placeholder built into a protocol.
Suppose we create a container protocol. We want to abstract the shared ability to “put in and take out elements” for stacks and queues, but the element type is the problem. IntStack contains Int, while StringQueue contains String, so the protocol cannot determine the element type. We declare it as a placeholder.
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
The conforming type fills the placeholder: IntStack uses Int for Item, and StringQueue uses String. In most cases, there is no need to explicitly write typealias Item = Int. The compiler infers it from append’s parameter type.
In fact, this is not a new concept. The standard library is built around associated types. Collection’s Element and Index, IteratorProtocol’s Element, and C.Element from the where clauses in the generics article all point to this placeholder. Even Equatable has a Self requirement (static func == (lhs: Self, rhs: Self) -> Bool), a cousin of associated types. In short, associated types are first-class citizens of the protocol world.
The difference from the generic <T> can be summarized in one line: a generic parameter is a placeholder filled by the caller, while an associated type is filled by the conforming type. Stack<Int> lets the caller choose Int, but Container’s Item is determined by the IntStack type itself.
Why the Error Occurred — The Box’s Specification Cannot Be Determined
Now we can dissect that infamous error. Why couldn’t a protocol with an associated type be used as a type?
In the some·any article, we called a protocol-typed variable an existential type—a box. Writing var c: Container means creating a box containing something conforming to Container. But a problem appears when we take an element out. What is the type of c[0]? It is Item, but Item depends on what is actually inside the box. It is Int for IntStack and String for StringQueue. The compiler cannot answer from the box alone.
An expression whose type cannot be determined is not allowed in a statically typed language, so older Swift blocked it at the entrance. The error really meant, “Use this protocol only as a constraint.” With generic <C: Container>, C is determined at the call site, and C.Item is determined with it, so there is no problem. The message was unfriendly, but the prescription—use a generic instead of a box—was accurate.
Language Evolution — A History of Locks Opening One by One
This inconvenience was infamous for years, and Swift gradually removed the locks through several proposals.
Swift 5.7, Swift Evolution proposal SE-0309. Protocols with associated types can now be used with any. var c: any Container compiles. However, Item taken from the box is treated as “an unknown type,” which limits what you can do with it. The door is open, but little can be done inside.
Around the same time, SE-0346 introduced primary associated types. This was the real game changer: protocols could expose their primary associated types in angle brackets.
protocol Container<Item> {
associatedtype Item
// ...
}
var numbers: any Container<Int> // Itema Int container box
func process(_ c: some Container<Int>) // Concise even in generics
any Container<Int> is a “box whose Item is fixed as Int.” The compiler knows that extracted elements are Int, dramatically increasing the box’s usefulness. The standard library was also refactored using this syntax. This is when forms such as any Collection<String> and some Sequence<Int> became possible.
It is useful to understand the direction of this evolution. The absolute rule “a protocol with associated types cannot be used as a type” became the precise rule “it can be used, but its capabilities are reduced in proportion to the associated types that remain unresolved.” It extends the philosophy seen in the some·any article: from prohibition to explicit cost.
Practical Patterns — Working with Associated Types
Let’s bring the theory into practical situations.
When designing: use an associated type where “each conforming type determines one different type.” A Repository protocol is a good example. With associatedtype Entity, UserRepository uses User and OrderRepository uses Order. If the caller should choose the type independently of the conforming type, use a generic function or type instead.
When consuming: prefer generic constraints; when storage or mixing is needed, use any with its primary associated type specified. Use a constraint such as func sync<R: Repository>(_ repo: R) where R.Entity == User first, and a stored value such as var repos: [any Repository<User>] second.
When blocked: use type erasure as a last resort—the AnyX pattern. If complex constraints remain unresolved even with primary associated types, manually erase the type by wrapping it in a concrete type, like the standard library’s AnySequence or Combine’s AnyPublisher. However, since SE-0346, this is needed far less often. Before writing a new AnyX wrapper, first check whether primary associated types solve the problem.
One more point: Self requirements. An operation such as == is meaningful only “between values of the same type,” so it is declared with Self. For this reason, two any Equatable boxes cannot be compared directly. Their contained types may differ. In such cases, the conventional solution is to abandon the box and use generics.
Summary
- Associated types are type placeholders built into protocols and filled by conforming types, usually through inference. The standard library is built on them, as with Collection’s Element.
- The old error: the entrance was blocked because the associated type of a value extracted from an existential box could not be determined. Generic constraints never had this problem.
- SE-0309 enabled any, while SE-0346’s primary associated types (
any Container<Int>) made boxes practical. - Practical order: generic constraints first, any with a specified primary associated type second, and manual type erasure as a last resort.
The next article covers a more hands-on topic: map, filter, reduce, and the differences between compactMap and flatMap, followed by performance with lazy sequences.
References
- SE-0309: Unlock existentials for all protocols
- SE-0346: Lightweight same-type requirements for primary associated types

![Cover image for [Swift Intermediate #4] Mastering Swift Associated Types](/assets/images/posts/843650cf-8f58-411e-9ef4-14639a9f6490/swift-associatedtype-1.jpg)