iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Generics

Generics let you write code that works with any type while keeping full compile-time type safety. You see them in Array, Dictionary, and your own functions and types. Constraints (`where`, `: Protocol`) limit what types are allowed, so you can call methods on a generic parameter without losing safety.

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…