How it works
The explanation, with examples you can run.
Generics and associated types
protocol Shape { var area: Double { get } }struct Square: Shape { var side: Double; var area: Double { side * side } }struct Circle: Shape { var r: Double; var area: Double { 3.14159 * r * r } } func largest<T: Shape>(_ items: [T]) -> T? { items.max { $0.area < $1.area } } protocol Store { associatedtype Item // each conforming type decides func load() -> [Item]}struct NumberStore: Store { func load() -> [Int] { [3, 1, 2] } } // Item is inferred as Int func first<S: Store>(of store: S) -> S.Item? { store.load().first } print(largest([Square(side: 1), Square(side: 3)])?.side ?? 0) // 3.0print(first(of: NumberStore()) ?? 0) // 3The compiler type-checks generic code once, and can specialise it for concrete types, so a generic function is usually as fast as a hand-written one. An associatedtype is a placeholder in a protocol, like Element in Collection or Body in SwiftUI's View. Since Swift 5.7, protocols can declare primary associated types, which allows some Collection<Int> and any Store<String>-style constraints in angle brackets.
some: opaque types
func makeShape() -> some Shape { Circle(r: 1) } // caller sees "some Shape", compiler knows Circle func describe(_ s: some Shape) -> String { // Swift 5.7: same as <T: Shape> "area \(s.area)"}print(describe(makeShape()))In return position, some Shape lets a function hide its concrete type while keeping it fixed: every path must return the same type, and the compiler keeps full type information, so there is no box. This is what var body: some View is: a deeply nested generic type that nobody wants to write out. In parameter position, some Shape is just shorter syntax for a generic parameter.
any: existentials
let mixed: [any Shape] = [Square(side: 2), Circle(r: 1)] // different types in one arraylet total = mixed.reduce(0) { $0 + $1.area }print(total)any Shape is an existential container: room for a small value inline (larger ones go to the heap), plus pointers to the type's metadata and witness table. Calls go through the table at runtime. That's the price of heterogeneity. The any keyword (Swift 5.6) makes the cost visible; it is required for protocols with associated types or Self requirements, and the stricter rule for all protocols is available as the upcoming feature ExistentialAny (still opt-in in the Swift 6 language mode). Since Swift 5.7 you can pass an any Shape to a function taking some Shape: the compiler opens the box for the call.
Choosing
- Start with
someor generics: one type per call, statically known, fastest. - Use
anywhen you really need different types side by side: a heterogeneous array, a stored property that can change type, a plugin list. - When
somewon't do because paths return different types, consider an enum with one case per type before reaching forany.
Swift 6.4 also allows some Shape? instead of (some Shape)? (SE-0521), a small change that makes optional opaque and existential types easier to write.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.