Skip to content

Generics

Generics allow you to write flexible, reusable code that works with multiple types while defining specific requirements for those types. This approach reduces duplication and clearly expresses your code's intent in an abstract way.

Generics power much of Swift's standard library. For instance, types like Array and Dictionary are generic, letting you store integers, strings, or any other type without restrictions on the type itself.

Why Use Generics?

Consider a simple function to swap two integer values:

swift
func swapTwoNumbers(_ a: inout Int, _ b: inout Int) {
    let temp = a
    a = b
    b = temp
}

This function uses in-out parameters to exchange values. You can call it like this:

swift
var num1 = 5
var num2 = 42
swapTwoNumbers(&num1, &num2)
print("num1 is now \(num1), num2 is now \(num2)")
// Prints "num1 is now 42, num2 is now 5"

It's handy but limited to integers. To swap strings or doubles, you'd need separate functions:

swift
func swapTwoTexts(_ a: inout String, _ b: inout String) {
    let temp = a
    a = b
    b = temp
}

func swapTwoFloats(_ a: inout Double, _ b: inout Double) {
    let temp = a
    a = b
    b = temp
}

These functions are identical except for the types. A generic version would be more efficient, handling any type as long as the values match.

Note: The types of a and b must be identical for swapping to work. Swift's type safety prevents mismatched swaps, causing compile-time errors.

Generic Functions

Generic functions adapt to any type. Here's a generic swap function:

swift
func swapAny<T>(_ a: inout T, _ b: inout T) {
    let temp = a
    a = b
    b = temp
}

Compare the signatures:

swift
func swapTwoNumbers(_ a: inout Int, _ b: inout Int)
func swapAny<T>(_ a: inout T, _ b: inout T)

The generic version uses T as a placeholder. It requires a and b to share the same type T, inferred at call time.

You can use it for various types:

swift
var num1 = 5
var num2 = 42
swapAny(&num1, &num2)
// num1 is now 42, num2 is now 5

var text1 = "apple"
var text2 = "banana"
swapAny(&text1, &text2)
// text1 is now "banana", text2 is now "apple"

Note: Swift's standard library includes a swap(_:_:) function, so use that in production code.

Type Parameters

Type parameters like T act as placeholders in function definitions, parameters, returns, or bodies. They're replaced by actual types at runtime. Use multiple parameters separated by commas, e.g., <T, U>.

Naming Type Parameters

Use descriptive names like Key or Value for clarity (e.g., in Dictionary<Key, Value>). For unrelated types, use single letters like T, U. Always use upper camel case to denote types.

Generic Types

Define custom generic classes, structs, or enums. Here's a generic Queue struct for any type:

swift
struct Queue<Element> {
    var elements: [Element] = []
    mutating func enqueue(_ element: Element) {
        elements.append(element)
    }
    mutating func dequeue() -> Element {
        return elements.removeFirst()
    }
}

Element is the placeholder. Create instances like Queue<String>():

swift
var queueOfFruits = Queue<String>()
queueOfFruits.enqueue("apple")
queueOfFruits.enqueue("banana")
queueOfFruits.enqueue("cherry")
// Queue now has 3 fruits

Dequeue removes the first item:

swift
let firstFruit = queueOfFruits.dequeue()
// firstFruit is "apple", queue now has 2 fruits

Extending a Generic Type

Extensions inherit the original type parameters:

swift
extension Queue {
    var firstElement: Element? {
        return elements.isEmpty ? nil : elements[0]
    }
}

Use firstElement without dequeuing:

swift
if let first = queueOfFruits.firstElement {
    print("First fruit is \(first).")
}
// Prints "First fruit is banana."

Type Constraints

Restrict type parameters to subclasses or protocols. Swift's Dictionary requires hashable keys via the Hashable protocol.

Syntax:

swift
func exampleFunc<T: BaseClass, U: Protocol>(t: T, u: U) {
    // Body
}

Example: A function to find an item's index, requiring equatability:

swift
func locateIndex<T: Equatable>(of target: T, in list: [T]) -> Int? {
    for (idx, val) in list.enumerated() {
        if val == target {
            return idx
        }
    }
    return nil
}

Use it:

swift
let animals = ["elephant", "tiger", "lion"]
let idx = locateIndex(of: "tiger", in: animals)
// idx is 1

Without Equatable, equality checks fail to compile.

Associated Types

Protocols can declare associated types with associatedtype:

swift
protocol Holder {
    associatedtype Content
    mutating func add(_ content: Content)
    var size: Int { get }
    subscript(i: Int) -> Content { get }
}

Conformers specify the type:

swift
struct IntQueue: Holder {
    var elements: [Int] = []
    mutating func add(_ content: Int) {
        elements.append(content)
    }
    var size: Int { return elements.count }
    subscript(i: Int) -> Int { return elements[i] }
    typealias Content = Int  // Optional due to inference
}

Generic types infer similarly.

Extend existing types:

swift
extension Array: Holder {}

Add constraints:

swift
protocol Holder {
    associatedtype Content: Equatable
    // ...
}

Protocols can self-reference:

swift
protocol SubHolder: Holder {
    associatedtype Sub: SubHolder where Sub.Content == Content
    func subSection(_ length: Int) -> Sub
}

Generic Where Clauses

Add requirements before a body:

swift
func matchAll<H1: Holder, H2: Holder>(_ one: H1, _ two: H2) -> Bool
    where H1.Content == H2.Content, H1.Content: Equatable {
    if one.size != two.size { return false }
    for i in 0..<one.size {
        if one[i] != two[i] { return false }
    }
    return true
}

Extensions with a Generic Where Clause

swift
extension Queue where Element: Equatable {
    func isFirst(_ item: Element) -> Bool {
        guard let first = elements.first else { return false }
        return first == item
    }
}

Or for specific types:

swift
extension Holder where Content == Float {
    func mean() -> Float {
        var total: Float = 0
        for i in 0..<size { total += self[i] }
        return total / Float(size)
    }
}

Contextual Where Clauses

Place where in declarations without full generics:

swift
extension Holder {
    func mean() -> Double where Content == Int {
        var total = 0.0
        for i in 0..<size { total += Double(self[i]) }
        return total / Double(size)
    }
    func isLast(_ item: Content) -> Bool where Content: Equatable {
        return size >= 1 && self[size-1] == item
    }
}

Equivalent to separate extensions.

Associated Types with a Generic Where Clause

swift
protocol Holder {
    // ...
    associatedtype Traverser: IteratorProtocol where Traverser.Element == Content
    func createTraverser() -> Traverser
}

Inherit and constrain:

swift
protocol SortedHolder: Holder where Content: Comparable { }

Generic Subscripts

swift
extension Holder {
    subscript<Indexes: Sequence>(indexes: Indexes) -> [Content]
        where Indexes.Iterator.Element == Int {
        var res: [Content] = []
        for idx in indexes { res.append(self[idx]) }
        return res
    }
}

Implicit Constraints

Generic code often implies conformance to protocols like Copyable. Suppress with ~:

swift
func process<Val: ~Copyable>(inout val: Val) {
    // Cannot copy val here
}

Released under the MIT License.