What are associated types in Swift protocols?
TL;DR
An associated type is a placeholder type in a protocol. You write associatedtype Item.
Each conforming type decides what Item really is. Swift can often infer it.
It makes protocols generic. Collection.Element and View.Body are associated types.
A simple example
protocol Container {
associatedtype Item
var items: [Item] { get }
mutating func add(_ item: Item)
}
struct IntBox: Container {
var items: [Int] = []
mutating func add(_ item: Int) { items.append(item) } // Item = Int (inferred)
}
struct StringBox: Container {
typealias Item = String // you can also say it explicitly
var items: [String] = []
mutating func add(_ item: String) { items.append(item) }
}Without an associated type you would need Any, and lose type safety.
Constraints on associated types
You can require the placeholder to conform to protocols.
protocol Repository {
associatedtype Model: Identifiable & Codable
func fetchAll() async throws -> [Model]
func save(_ model: Model) async throws
}You can also use where clauses:
protocol Graph {
associatedtype Node: Hashable
associatedtype Edges: Sequence where Edges.Element == Node
func neighbors(of node: Node) -> Edges
}Using these protocols in functions
The usual way is generics. The compiler knows the real type.
func printAll<C: Container>(_ box: C) where C.Item: CustomStringConvertible {
for item in box.items { print(item.description) }
}Or the short form with some:
func countItems(_ box: some Container) -> Int {
box.items.count
}Primary associated types
Since Swift 5.7 a protocol can name its main associated type in angle brackets.
protocol Container<Item> {
associatedtype Item
var items: [Item] { get }
}
func firstNumber(in box: some Container<Int>) -> Int? {
box.items.first
}Standard library examples: Collection<Element>, Sequence<Element>, AsyncSequence<Element, Failure>.
Can you store it as any Container?
Before Swift 5.7, no. You got the famous error: "Protocol can only be used as a generic constraint because it has Self or associated type requirements."
Now you can write any Container. But the associated type is erased. You cannot call add(_:), because Swift does not know what Item is.
let boxes: [any Container] = [IntBox(), StringBox()]
boxes[0].items.count // OK: count is Int
// boxes[0].add(5) // error: Item is unknownWith a primary associated type you can keep the element type:
let intBoxes: [any Container<Int>] = [IntBox()]Type erasure (the older pattern)
Before any, people wrote wrapper types like AnyPublisher or AnySequence. They hide the concrete type but keep the element type.
struct AnyContainer<Item>: Container {
private let getItems: () -> [Item]
init<C: Container>(_ base: C) where C.Item == Item {
getItems = { base.items }
}
var items: [Item] { getItems() }
mutating func add(_ item: Item) { /* forward or ignore */ }
}You still see this in Combine and older codebases. Interviewers may ask you to explain it.
Associated type vs generic parameter
| Associated type | Generic type parameter | |
|---|---|---|
| Where | Inside a protocol | On a struct, class, enum or function |
| Who picks it | The conforming type, once | The caller, each time |
| Example | Collection.Element | Array<Element> |
A type can conform to a protocol only one way. So IntBox cannot be a Container of both Int and String.
Common follow-up questions
- Why can't protocols use generic parameters like
protocol Container<T>? The angle brackets on a protocol only name primary associated types. The conforming type still chooses them, once. - What does
Selfmean in a protocol? It is the conforming type. Like an associated type, it limits how you can use the protocol asany. - How does Swift infer associated types? It looks at the types used in the methods and properties you implement.
- What is
some View? An opaque type.Viewhas an associated typeBody, andsomehides which exact type it is. - When should you use type erasure today? When you need to store mixed types and still call methods that use the associated type. Often
any P<T>is enough now.