What are Generics in Swift and why are they needed
Generics (generic programming) are one of the most powerful features of the Swift language. They allow you to write flexible, reusable code that works with any data type while maintaining strong type safety. Instead of creating separate functions or structures for Int, String, Double, and other types, you write one universal piece of code that adapts to the specific type at compile time.
Imagine you need to write a function that swaps two values. Without Generics, you would have to create overloads for each type:
func swapInts(_ a: inout Int, _ b: inout Int) { let temp = a a = b b = temp}
func swapStrings(_ a: inout String, _ b: inout String) { let temp = a a = b b = temp}With Generics, this is done in one line:
func swapValues<T>(_ a: inout T, _ b: inout T) { let temp = a a = b b = temp}
var x = 10, y = 20swapValues(&x, &y) // works with Int
var str1 = "Hello", str2 = "World"swapValues(&str1, &str2) // works with StringMain advantages of Generics
- Type safety: the compiler checks types at compile time, eliminating runtime errors
- Code reuse: code written once works with any types
- Performance: Generics in Swift are implemented at the compiler level, without loss of execution speed
- Readability: reduces code duplication, makes it more concise
Generic functions and type parameters
A Generic function is declared using angle brackets <>, inside which a type parameter is specified (usually denoted by the letter T, but any names can be used). The type parameter becomes a placeholder that is replaced with the actual type when the function is called.
Example: finding an element in an array
func findIndex<T: Equatable>(of value: T, in array: [T]) -> Int? { for (index, element) in array.enumerated() { if element == value { return index } } return nil}
let numbers = [1, 5, 3, 12, 7]if let index = findIndex(of: 12, in: numbers) { print("Index of number 12: \\(index)") // Index of number 12: 3}
let names = ["Anna", "Boris", "Victor"]if let index = findIndex(of: "Boris", in: names) { print("Index of name Boris: \\(index)") // Index of name Boris: 1}Notice the constraint T: Equatable. This is a type constraint — we require that type T supports the Equatable protocol, otherwise the comparison operation == would be impossible.
Generic types: structures, classes, and enumerations
Generics work not only with functions but also with custom types. This allows you to create universal data containers.
Creating a Generic stack
A classic example is the Stack data structure, which works with any type:
struct Stack<Element> { private var items: [Element] = [] mutating func push(_ item: Element) { items.append(item) } @discardableResult mutating func pop() -> Element? { return items.popLast() } func peek() -> Element? { return items.last } var isEmpty: Bool { return items.isEmpty }}
// Usagevar intStack = Stack<Int>()intStack.push(10)intStack.push(20)print(intStack.pop() ?? "") // 20
var stringStack = Stack<String>()stringStack.push("Swift")stringStack.push("Generics")print(stringStack.pop() ?? "") // GenericsGeneric enumerations
Enumerations can also be