Generics, associated types, some & any in Swift
A practical study guide to generic constraints, associatedtype, opaque types and existential trade-offs.
Why generics?
Generics remove duplication without discarding compile-time type information. A generic parameter relates inputs and outputs, and constraints like Equatable or Sendable express the operations you need. Don't add generic parameters without a real reason.
func firstMatch<T: Equatable>(_ items: [T], value: T) -> T? {
items.first { $0 == value }
}
let answer = firstMatch([1, 3, 5], value: 3)associatedtype describes a type relationship
A protocol can declare associatedtype when the specific element type is determined by each conforming implementation. This is not a generic protocol instantiated as Repository<Account>; it is a requirement that each conformer specifies its own Model.
protocol Repository {
associatedtype Model
func load() async throws -> [Model]
}
struct Account { let id: String }
struct AccountRepository: Repository {
func load() async throws -> [Account] { [Account(id: "1")] }
}
func loadAll<R: Repository>(from repo: R) async throws -> [R.Model] {
try await repo.load()
}some Protocol vs any Protocol
A generic parameter (or some in parameter position) preserves the concrete type relationship. An opaque return some P hides a single concrete return type selected by the implementation. An existential any P boxes a runtime-selected conforming value, which is useful for dependency injection or heterogeneous collections, but can lose operations tied to Self or associated types.
protocol Greeter {
func greet() -> String
}
struct EnglishGreeter: Greeter {
func greet() -> String { "Hello" }
}
func makeGreeter() -> some Greeter { EnglishGreeter() }
func speak(using greeter: any Greeter) -> String {
greeter.greet()
}| Syntax | Who chooses type? | Trade-off |
|---|---|---|
| T: P / some P parameter | Caller (generic) | Type relationships preserved |
| some P return | Implementation | Single hidden concrete return type |
| any P value | Runtime / dependency provider | Flexible, existential boxing |
| associatedtype | Conforming type | Requires type relationships to be handled |
Common pitfalls
An opaque return type cannot arbitrarily return unrelated concrete types from branches unless you introduce a wrapper. any P is not the same as a generic constraint and not every protocol method works on an existential without opening it. Prefer a concrete type at a simple boundary and introduce type erasure only when runtime heterogeneity is needed.
Personal study notes. Check details against your target Swift/iOS version and official documentation.