Protocols with associatedtype and where constraints are a powerful type abstraction mechanism in Swift. They are often used to implement generic protocols, where the specific type is defined by the implementation. Historically, in earlier versions of Swift, protocols with associatedtype could not be used as concrete types (existential types), which imposed limitations on the use of such protocols in collections and interfaces. Later, Swift added mechanisms to restrict associatedtype using where clauses, allowing for the creation of flexible and safe abstractions for complex scenarios.
Problem: Often there is a need to create a protocol that allows abstraction from specific collections or data handlers. However, standard protocols may not be flexible enough: not all types can be generalized without knowledge of related types, and the logic of protocol inheritance with associatedtype is not always obvious.
Solution: Use where constraints to clarify requirements for implementations, allowing for the creation of protocols with adaptive behavior.
Example code:
protocol Storage { associatedtype Element func add(_ item: Element) } // Constrained protocol for storing only numbers protocol NumericStorage: Storage where Element: Numeric { func sum() -> Element } struct IntStorage: NumericStorage { private var items: [Int] = [] func add(_ item: Int) { items.append(item) } func sum() -> Int { items.reduce(0, +) } }
Key features:
Can a protocol with associatedtype be used as a type (e.g., for a collection)?
No, it cannot be done directly. A protocol with associatedtype is a PAT (protocol with associated type) and cannot be used as an existential type. For example, you cannot declare an array [Storage], only by using type erasure.
How to implement type-erasure for a protocol with associatedtype?
Through a helper wrapper that hides the actual type implementation.
struct AnyStorage<T>: Storage { private let _add: (T) -> Void init<S: Storage>(_ storage: S) where S.Element == T { _add = storage.add } func add(_ item: T) { _add(item) } }
What is the difference between associatedtype and a generic parameter of a protocol?
associatedtype defines a specific type that must be implemented accordingly, while a generic parameter is explicitly specified in the protocol or function itself but is not allowed in the protocol declaration. A protocol cannot be generic by syntax, only through associatedtype.
A developer attempted to use [Storage] to store any collections. The code does not compile, leading to the need for implicit casts or the use of Any/unsafe approaches.
Pros:
Cons:
A developer wrapped AnyStorage<T> to hide the specific implementation and added where constraints to ensure correct operation only with the required types.
Pros:
Cons: