Blog / February 22, 2023 / 3 mins read / By Mahi Garg

Generics in Swift

Generics allow us to write functions, structures, and classes that can work with different types while maintaining type safety. Rather than specifying concrete types, we use placeholders (type parameters) that get substituted with actual types when the code is used. This flexibility makes generics a valuable tool for writing versatile and adaptable code.

Generic Functions in Swift

Let’s begin with a basic example of a generic function that swaps two values of any type:

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

// Usage
var num1 = 5
var num2 = 10
swapValues(&num1, &num2)
print("num1: \(num1), num2: \(num2)") // Output: "num1: 10, num2: 5"

var str1 = "Hello"
var str2 = "World"
swapValues(&str1, &str2)
print("str1: \(str1), str2: \(str2)") // Output: "str1: World, str2: Hello"

The swapValues function uses a generic type parameter T in angle brackets (<T>) and performs a value swap on two arguments of the same type.

Generic Structures and Classes

Generics are not limited to functions; we can also create generic structures and classes. Let’s see an example of a generic Stack data structure:

struct Stack<Element> {
    private var elements: [Element] = []

    mutating func push(_ element: Element) {
        elements.append(element)
    }

    mutating func pop() -> Element? {
        return elements.popLast()
    }
}

// Usage
var intStack = Stack<Int>()
intStack.push(1)
intStack.push(2)
intStack.push(3)

print(intStack.pop()) // Output: Optional(3)

var stringStack = Stack<String>()
stringStack.push("Swift")
stringStack.push("is")
stringStack.push("awesome!")

print(stringStack.pop()) // Output: Optional("awesome!")

In this example, we define a generic Stack structure that can hold elements of any type. We use the type parameter Element to represent the placeholder type.

Constraints on Generics

Swift allows us to impose constraints on generic types to restrict them to specific requirements. For instance, we can add constraints to ensure that the generic type conforms to a protocol or inherits from a particular class.

func printDetails<T: CustomStringConvertible>(_ item: T) {
    print(item.description)
}

// Usage
let number = 42
printDetails(number) // Output: "42"

let names = ["Alice", "Bob", "Charlie"]
printDetails(names) // Output: "["Alice", "Bob", "Charlie"]"

In this example, the printDetails function takes a generic parameter T, constrained to types conforming to the CustomStringConvertible protocol. This constraint guarantees that we can safely access the description property of the input.

Conclusion

Generics in Swift are a powerful and essential tool for creating flexible and reusable code. By using type placeholders, we can write functions, structures, and classes that work with multiple types while maintaining type safety. Embracing generics allows developers to create adaptable, efficient, and maintainable code, making Swift an even more robust and developer-friendly language.

Comments