← Knowledge hub
Architecture · ORIGINAL STUDY NOTE

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.

A constrained generic function
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.

Repository with an associated type
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.

Opaque result and existential dependency
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()
}
Which abstraction?
SyntaxWho chooses type?Trade-off
T: P / some P parameterCaller (generic)Type relationships preserved
some P returnImplementationSingle hidden concrete return type
any P valueRuntime / dependency providerFlexible, existential boxing
associatedtypeConforming typeRequires 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.

SOURCES & DOCUMENTATION

Personal study notes. Check details against your target Swift/iOS version and official documentation.