Generics
Prerequisites: Protocols, Extensions
Generics let you write one function or type that works with many
types, while keeping full type safety. Array<Element> and
Dictionary<Key, Value> are generic types you've used all along.
A generic function
func firstAndLast<T>(_ items: [T]) -> (T, T)? {
guard let first = items.first, let last = items.last else {
return nil
}
return (first, last)
}
if let pair = firstAndLast([3, 7, 9]) {
print(pair)
}
if let pair = firstAndLast(["a", "b"]) {
print(pair)
}
(3, 9)
("a", "b")
<T> introduces a type parameter — a placeholder filled in at each
call. With [Int] it's Int; with [String] it's String. Unlike
using Any, the result keeps its real type.
Constraints
Often a generic function needs its type to be able to do something. A constraint requires a protocol:
func largest<T: Comparable>(_ items: [T]) -> T? {
var best = items.first
for item in items where item > best! {
best = item
}
return best
}
print(largest([4, 9, 2]) ?? 0)
print(largest(["pear", "apple", "zucchini"]) ?? "")
9
zucchini
Without T: Comparable, the > inside wouldn't compile — not every
type can be compared.
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 isEmpty: Bool { items.isEmpty }
}
var history = Stack<String>()
history.push("home")
history.push("settings")
print(history.pop() ?? "")
print(history.top ?? "")
settings
home
Common mistakes
- Reaching for generics too early. Write the concrete version first; make it generic when you actually need a second type.
- Using
Anyinstead of generics, which throws away type information and forces casts later.