Skip to content
AgileLogics
6 min read

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 Any instead of generics, which throws away type information and forces casts later.