Lesson 91Advanced Topics

Generic Functions

What are Generics?

Generics let you write flexible, reusable functions and types that work with any type. Instead of writing separate functions for Int, String, etc., write one generic function that works with all types!

Syntax

func functionName<T>(param: T) -> T

T is a type placeholder - Swift infers the actual type from usage.

Type Constraints

Protocol Constraint

<T: Equatable>

T must conform to protocol

Multiple Constraints

<T: A & B>

T must conform to both

Where Clause

where T.Element == Int

Complex constraints

Multiple Types

<T, U>

Multiple type parameters

Common Generic Functions

  • swap(_:_:) - Swap two values
  • min(_:_:) / max(_:_:) - Compare values
  • Array.map, filter, reduce
  • Optional.map, flatMap

Benefits

  • Type Safety - Compiler checks types
  • Reusability - One function for many types
  • Performance - No runtime overhead
  • Clarity - Explicit type relationships
main.swift
// GENERIC FUNCTIONS
// Write flexible, reusable code that works with any type

// ===== BASIC GENERIC FUNCTION =====
func swapValues<T>(_ a: inout T, _ b: inout T) {
    let temp = a
    a = b
    b = temp
}

var x = 5, y = 10
swapValues(&x, &y)
print("x: \(x), y: \(y)")  // x: 10, y: 5

var s1 = "Hello", s2 = "World"
swapValues(&s1, &s2)
print("s1: \(s1), s2: \(s2)")  // s1: World, s2: Hello

// ===== GENERIC FUNCTION WITH RETURN =====
func firstElement<T>(of array: [T]) -> T? {
    return array.first
}

let numbers = [1, 2, 3]
let first = firstElement(of: numbers)  // Optional(1)

let names = ["Alice", "Bob"]
let firstName = firstElement(of: names)  // Optional("Alice")

// ===== MULTIPLE TYPE PARAMETERS =====
func makePair<T, U>(_ first: T, _ second: U) -> (T, U) {
    return (first, second)
}

let pair1 = makePair(1, "one")      // (Int, String)
let pair2 = makePair("key", 42)     // (String, Int)
let pair3 = makePair(true, 3.14)    // (Bool, Double)

// ===== TYPE CONSTRAINTS =====
// Require types to conform to protocols

func findIndex<T: Equatable>(of value: T, in array: [T]) -> Int? {
    for (index, item) in array.enumerated() {
        if item == value {
            return index
        }
    }
    return nil
}

let index = findIndex(of: "Bob", in: ["Alice", "Bob", "Charlie"])
print("Index: \(index ?? -1)")  // Index: 1

// ===== MULTIPLE CONSTRAINTS =====
func printSorted<T: Comparable & CustomStringConvertible>(_ array: [T]) {
    for item in array.sorted() {
        print(item.description)
    }
}

// ===== WHERE CLAUSE =====
func allEqual<T: Equatable>(_ a: T, _ b: T, _ c: T) -> Bool
    where T: Comparable {
    return a == b && b == c
}

print(allEqual(1, 1, 1))  // true
print(allEqual(1, 2, 1))  // false

// ===== GENERIC WITH ASSOCIATED TYPE =====
func sum<T: Sequence>(_ sequence: T) -> Int
    where T.Element == Int {
    return sequence.reduce(0, +)
}

print(sum([1, 2, 3, 4, 5]))  // 15
print(sum(1...10))           // 55

// ===== SPECIALIZED GENERIC FUNCTIONS =====
// Works with any numeric type
func maximum<T: Comparable>(_ a: T, _ b: T) -> T {
    return a > b ? a : b
}

print(maximum(5, 3))        // 5
print(maximum(3.14, 2.71))  // 3.14
print(maximum("a", "z"))    // z

// ===== GENERIC ALGORITHMS =====
func filter<T>(_ array: [T], where condition: (T) -> Bool) -> [T] {
    var result: [T] = []
    for item in array {
        if condition(item) {
            result.append(item)
        }
    }
    return result
}

let evens = filter([1, 2, 3, 4, 5]) { $0 % 2 == 0 }
print(evens)  // [2, 4]

// ===== TYPE INFERENCE =====
// Swift infers T from usage
let result1 = firstElement(of: [1, 2, 3])      // T = Int
let result2 = firstElement(of: ["a", "b"])     // T = String

// Explicit type annotation
let result3: Int? = firstElement(of: [1, 2, 3])

Try It Yourself!

Create a generic Stack<T> data structure with push, pop, and peek methods!