Generics let one implementation work for many types with full type safety, as Array and Dictionary do.
A generic function has type parameters in angle brackets: func first<T>(_ xs: [T]) -> T?. The compiler infers T at each call and checks types as if you'd written a specific version.
Constraints say what a type parameter must support: func largest<T: Comparable>(_ xs: [T]) -> T? can use <. Longer requirements go in a where clause: where T: Hashable, U.Element == T.
Generic types work the same way: struct Stack<Element> { private var items: [Element] = [] }. Extensions can add members for specific constraints: extension Stack where Element: Equatable.
Protocols use associated types instead of angle brackets: Collection has associatedtype Element. A conforming type fixes it, often by inference. You constrain associated types in generic code: func sum<C: Collection>(_ c: C) -> Int where C.Element == Int. Swift 5.7 added primary associated types, so you can write some Collection<Int> or any Sequence<String> directly.
The shorthand func process(_ items: some Collection<Int>) is equivalent to a generic parameter — it is the same as writing <C: Collection<Int>>.
Generics are specialized by the optimizer when the concrete type is visible, so they are usually as fast as hand-written code; across module boundaries they may use a slower dictionary-passing path unless the function is @inlinable.
Example
struct Stack<Element> {
private var items: [Element] = []
mutating func push(_ x: Element) { items.append(x) }
mutating func pop() -> Element? { items.popLast() }
var top: Element? { items.last }
}
extension Stack where Element: Numeric {
var total: Element { items.reduce(0, +) }
}
func largest<T: Comparable>(_ xs: [T]) -> T? {
guard var best = xs.first else { return nil }
for x in xs where x > best { best = x }
return best
}
func countMatches(in items: some Sequence<String>, prefix: String) -> Int {
items.filter { $0.hasPrefix(prefix) }.count
}
var s = Stack<Int>()
s.push(3); s.push(4)
print(s.top ?? 0, s.total) // 4 7
print(largest(["pear", "apple", "zebra"]) ?? "") // zebra
print(countMatches(in: ["swift", "sql", "go"], prefix: "s")) // 2Remember
- Type parameters plus constraints give reusable, type-safe code
- Protocols use associated types; constrain them in
whereclauses some P<T>in a parameter is shorthand for a generic parameter
Watch out for
- Using
[Any]orany Pwhere a generic would keep full type information.