What are generics in Swift, and how do constraints work?
TL;DR
Generics let you write code once and use it with many types, while keeping full type safety.
A constraint says what the type must be able to do, like T: Comparable.
Use where clauses for more complex rules. Array, Dictionary and Optional are all generic.
A generic function
func swapPair<T>(_ pair: (T, T)) -> (T, T) {
(pair.1, pair.0)
}
swapPair((1, 2)) // (2, 1)
swapPair(("a", "b")) // ("b", "a")T is a type parameter. Swift infers it from the arguments. Both values must be the same type.
Adding a constraint
Without a constraint, you can do almost nothing with T. You cannot compare it or add it.
func largest<T: Comparable>(_ items: [T]) -> T? {
guard var best = items.first else { return nil }
for item in items where item > best { best = item }
return best
}
largest([3, 9, 2]) // 9
largest(["pear", "apple"]) // "pear"T: Comparable lets you use >.
A generic type
struct Stack<Element> {
private var items: [Element] = []
mutating func push(_ item: Element) { items.append(item) }
mutating func pop() -> Element? { items.popLast() }
var top: Element? { items.last }
}
var numbers = Stack<Int>()
numbers.push(1)
var words = Stack<String>()
words.push("hi")where clauses
Use where when you need more than one rule, or rules about associated types.
func allEqual<C: Collection>(_ items: C) -> Bool where C.Element: Equatable {
guard let first = items.first else { return true }
return items.allSatisfy { $0 == first }
}
func merge<A: Sequence, B: Sequence>(_ a: A, _ b: B) -> [A.Element]
where A.Element == B.Element {
Array(a) + Array(b)
}Constrained extensions
Add methods only when the generic type meets a condition.
extension Stack where Element: Numeric {
func sum() -> Element { items.reduce(0, +) } // same file, so private items is visible
}Conditional conformance
A generic type conforms to a protocol only when its parameter does.
extension Stack: Equatable where Element: Equatable { }
// Stack<Int> is Equatable. Stack<UIView> is not.This is why [Int] is Equatable but [Any] is not.
Short syntax with some
Since Swift 5.7, some in a parameter is a shorter generic.
func printAll(_ items: some Collection<String>) {
for item in items { print(item) }
}
// Same as:
func printAll<C: Collection>(_ items: C) where C.Element == String { }Use the long form when two parameters must share one type.
Generics vs Any vs any Protocol
Generic T | any Protocol | Any | |
|---|---|---|---|
| Type known at compile time | Yes | No | No |
| Needs casting | No | Sometimes | Yes |
| Speed | Fastest (can specialize) | Slower (box + dynamic dispatch) | Slowest |
| Mixed types in one array | No | Yes | Yes |
How generics run
Swift compiles a generic function once and passes type information at run time. When the optimizer can see the concrete type, it specializes the function: it creates a version just for Int, for example. Then calls are as fast as normal code.
@inlinable lets other modules specialize your generic code. It is used in libraries like the standard library.
Common mistakes
- Using
Anyinstead of a generic. You lose type safety and need casts. - Over-generic code. If only one type uses it, a plain type is clearer.
- Forgetting that
Tis fixed per call.largest([1, "a"])does not compile.
Common follow-up questions
- What is the difference between a generic and an associated type? The caller picks a generic parameter. The conforming type picks an associated type.
- What are parameter packs? Swift 5.9 added
each Tandrepeatto write functions that take any number of different types, likefunc all<each T>(_ v: repeat each T). - Can generics work with noncopyable types? Yes, since Swift 6, with
T: ~Copyable. By default every generic parameter is assumed copyable. - What is a generic constraint on a class?
T: UIViewmeansTmust beUIViewor a subclass.T: AnyObjectmeans any class. - Why do some generic errors look so long? The compiler tries many overloads during type inference. Adding explicit types often gives clearer errors and faster builds.