Generics
Generics let you write code that works with any type while keeping full compile-time type safety. You see them in Array
Generic functions, types, constraints, and where
EXAMPLE
import Foundation
// 1) Generic function
func swapValues<T>(_ a: inout T, _ b: inout T) {
let tmp = a; a = b; b = tmp
}
var x = 1, y = 2
swapValues(&x, &y) // works for Int
var s1 = "hi", s2 = "there"
swapValues(&s1, &s2) // and for String
// 2) Generic type with a constraint
struct Stack<Element> {
private var items: [Element] = []
var isEmpty: Bool { items.isEmpty }
mutating func push(_ x: Element) { items.append(x) }
mutating func pop() -> Element? { items.popLast() }
}
var ints = Stack<Int>()
ints.push(1); ints.push(2); print(ints.pop() ?? -1) // 2
// 3) Multiple constraints with where
func uniqueSorted<T: Hashable & Comparable, S: Sequence>(_ xs: S) -> [T] where S.Element == T {
Array(Set(xs)).sorted()
}
print(uniqueSorted([3, 1, 2, 1, 3, 2])) // [1, 2, 3]
print(uniqueSorted(["a", "b", "a"])) // ["a", "b"]
// 4) Protocol with an associated type
protocol Cache {
associatedtype Key: Hashable
associatedtype Value
mutating func set(_ key: Key, _ value: Value)
func get(_ key: Key) -> Value?
}
struct LRU<K: Hashable, V>: Cache {
private(set) var capacity: Int
private var store: [K: V] = [:]
private var order: [K] = []
init(capacity: Int) { self.capacity = capacity }
mutating func set(_ key: K, _ value: V) {
if store[key] == nil { order.append(key) }
store[key] = value
if order.count > capacity {
let evict = order.removeFirst()
store.removeValue(forKey: evict)
}
}
func get(_ key: K) -> V? { store[key] }
}
var c = LRU<String, Int>(capacity: 2)
c.set("a", 1); c.set("b", 2); c.set("c", 3)
print(c.get("a") as Any) // nil — evicted
// 5) Opaque return types — "some Protocol" hides the concrete type
func makeNumbers() -> some Sequence<Int> {
stride(from: 0, to: 10, by: 2)
}
for n in makeNumbers() { print(n, terminator: " ") } // 0 2 4 6 8
Why it matters
Reach for `some Protocol` over a boxed `any Protocol` whenever the concrete type is known at compile time — `some` keeps the call inlinable and zero-cost, while `any` allocates an existential box. Use `any` only when you genuinely need a heterogeneous collection.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
func largest<T: Comparable>(_ xs: [T]) -> T? {
xs.max()
}
print(largest([3, 1, 4, 1, 5]) ?? -1)
Try it Yourself »
Discussion
Loading…