Skip to Content
Course content

27: Generic Constraints

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Up until now, we've treated generics as a way to make our code flexible. You've learned that T can be anything—an Int, a String, or some custom User struct. But here is the reality of professional development: "anything" is often too broad. If you try to actually do something with that generic type, you'll quickly run into a wall.

struct ScoreBoard<T> {
    var scores: [T]

    func findHighestScore() -> T? {
        guard var highest = scores.first else { return nil }
        
        for score in scores {
            if score > highest { // ❌ ERROR HERE
                highest = score
            }
        }
        return highest
    }
}

The "Binary Operator" Wall

If you try to compile the code above, Swift is going to yell at you with an error that looks something like: "Binary operator '>' cannot be applied to two 'T' operands."

I've seen a lot of learners get frustrated by this. You're thinking, "But I'm only going to use this with numbers! Why is the compiler complaining?" The problem is that the compiler doesn't know that. From Swift's perspective, T could be a UIImage or a NetworkRequest. You can't use the "greater than" operator on a image or a request. It doesn't make sense.

In Swift, the > and < operators aren't just magic; they are requirements of a specific protocol called Comparable. Since our T is currently "anything," it doesn't guaranteed that it conforms to Comparable.

Telling Swift what T can actually do

To fix this, we use a Generic Constraint. Instead of telling Swift that T can be any type, we tell it that T must be a type that conforms to Comparable. We do this by adding a colon after the type parameter.

struct ScoreBoard<T: Comparable> {
    var scores: [T]

    func findHighestScore() -> T? {
        guard var highest = scores.first else { return nil }
        
        for score in scores {
            if score > highest { // ✅ This now works!
                highest = score
            }
        }
        return highest
    }
}

By adding : Comparable, you've essentially created a contract. You're telling the compiler: "I don't care exactly what T is, as long as it knows how to compare itself to another instance of the same type." Now, the > operator is legal because the compiler knows every possible T that enters this struct will support it.

Handling Multiple Constraints with 'where'

Sometimes, one constraint isn't enough. Maybe you need your type to be Comparable so you can sort it, but you also need it to be Numeric so you can perform math on it. While you can comma-separate constraints in the angle brackets, it quickly becomes a mess to read.

This is where the where clause comes in. It's a much cleaner way to define complex requirements, especially when the constraint depends on a relationship between two different generic types.

func calculateAverage<T>(items: [T]) -> T where T: BinaryInteger {
    let sum = items.reduce(0, +)
    return sum / T(items.count)
}

I personally prefer the where clause for anything more complex than a single protocol. It keeps the function signature readable and separates the "what it is" (the type) from the "what it must do" (the constraint).




📋 Practical Task

Building a Generic Top-K Filter

Your task is to create a utility function that extracts the "Top K" elements from a collection. This is a common pattern in dashboards (e.g., "Top 5 High Scores" or "Top 3 Most Recent Posts").

Requirements:

  • Create a function called topElements that takes two arguments: an array of generic type T and an integer k.
  • Use a generic constraint to ensure that T is Comparable.
  • The function should sort the array in descending order and return a new array containing only the first k elements.
  • Ensure the function handles cases where k is larger than the actual size of the array without crashing.

Test your code with:

let numbers = [12, 45, 2, 89, 34]
let top3Numbers = topElements(items: numbers, k: 3) // Should be [89, 45, 34]

let words = ["Apple", "Zebra", "Banana", "Mango"]
let top2Words = topElements(items: words, k: 2) // Should be ["Zebra", "Mango"]
Rating
0 0

There are no comments for now.

to be the first to leave a comment.