Generics and Constraints (with version cues)#
Practical patterns for generics, protocol constraints, and existential usage.
Version Quick Reference#
- Swift 5.6+:
any keyword required to spell existential types explicitly. - Swift 5.7 (Xcode 14): primary associated types, improved existential type inference, better
Sendable checks behind flags. - Swift 5.9 (Xcode 15): parameter packs and variadic generics (advanced; skip unless you need them).
- Swift 6+: strict concurrency checking is the default; existential/
Sendable diagnostics are stricter.
- Use
where to keep function signatures clear and constraints localized. - Conditional conformance lets you add protocol adoption only when generic parameters meet requirements.
1
2
3
4
5
6
| extension Array: Encodable where Element: Encodable {}
func merge<P: Sequence, Q: Sequence>(_ lhs: P, _ rhs: Q) -> [P.Element]
where P.Element == Q.Element {
lhs + rhs
}
|
Primary Associated Types (Swift 5.7)#
- Protocols can surface key associated types in angle brackets, reducing boilerplate when constraining existentials.
1
2
3
4
5
6
7
8
| protocol IteratorProtocol<Element> {
associatedtype Element
mutating func next() -> Element?
}
// Prior to 5.7: `any IteratorProtocol` lost the element type easily.
// With a primary associated type you can write:
let ints: any IteratorProtocol<Int>
|
Existentials vs Opaque Types#
any creates a type-erased box; some preserves a single concrete type.- Prefer
some for return types when the function always yields one concrete conformer; use any when multiple conformers must flow through the same API or be stored together.
1
2
| func makeNumber() -> some Numeric { 42 } // static dispatch, concrete type hidden
let numbers: [any Numeric] = [1, 2.0, 3.0] // heterogeneous storage
|
Protocols with Self or Associated Types#
- Protocols referencing
Self or an associated type in requirements cannot be used as plain existentials without extra information. - Reach for:
- Generics on functions or types:
func render<P: View>(_ view: P) -> some View - Type erasure wrappers:
struct AnyProvider<Item>: Provider { ... } - Opaque returns when a single concrete type is guaranteed.
Constraint Hygiene Tips#
- Keep constraints close to the API that needs them; avoid lifting all constraints to top-level types.
- Prefer extension-based conformances to keep core types small and reusable.
- If a type has many conditional conformances, document the minimal required constraints to avoid surprises for callers.
Testing#
- Add focused tests around type-erased wrappers to ensure requirements are forwarded.
- For APIs relying on constraints, add compile-time checks via unused functions in test targets (e.g., a generic helper that must type-check).