๐ŸŽ“ A1Learner
๐Ÿ”
0 of 36 lessons completed

Swift Type Constraints

A type constraint is a limit placed on a generic placeholder. It names a protocol that the filling type must conform to.

Searching with an Equatable constraint

The firstIndex function is constrained to Equatable, so it can compare each element with ==. A manual counter tracks the position while the loop walks the array, and a missing value gives back -1.

func firstIndex<T: Equatable>(of target: T, in items: [T]) -> Int {
    var index = 0
    for item in items {
        if item == target {
            return index
        }
        index += 1
    }
    return -1
}
print(firstIndex(of: 30, in: [10, 20, 30, 40]))
print(firstIndex(of: "cy", in: ["ana", "bo", "cy"]))
print(firstIndex(of: 99, in: [10, 20]))

The largest with a Comparable constraint

The largest function is constrained to Comparable, so it can order its three values with >. It returns the greatest of the numbers and then the greatest of the words.

func largest<T: Comparable>(_ first: T, _ second: T, _ third: T) -> T {
    var best = first
    if second > best {
        best = second
    }
    if third > best {
        best = third
    }
    return best
}
print(largest(3, 9, 5))
print(largest("apple", "pear", "fig"))

A call that breaks the constraint

The Plain struct does not conform to Equatable, so it cannot fill the placeholder of firstIndex. The compiler rejects the call, and the stated output is the first error line from that compile.

struct Plain {
    var value: Int
}
func firstIndex<T: Equatable>(of target: T, in items: [T]) -> Int {
    var index = 0
    for item in items {
        if item == target {
            return index
        }
        index += 1
    }
    return -1
}
let items = [Plain(value: 1), Plain(value: 2)]
print(firstIndex(of: Plain(value: 1), in: items))

Example

func firstIndex<T: Equatable>(of target: T, in items: [T]) -> Int {
    var index = 0
    for item in items {
        if item == target {
            return index
        }
        index += 1
    }
    return -1
}
func largest<T: Comparable>(_ first: T, _ second: T, _ third: T) -> T {
    var best = first
    if second > best {
        best = second
    }
    if third > best {
        best = third
    }
    return best
}
print(firstIndex(of: "bo", in: ["ana", "bo", "cy"]))
print(largest(12, 7, 20))

Key points

  • A constraint is written after the placeholder name, with a colon and the protocol name.
  • An Equatable constraint lets a function compare values with == inside its body.
  • A Comparable constraint lets a function order values with < and > inside its body.
  • A type that does not conform to the named protocol cannot fill the placeholder.
  • When a call breaks a constraint, the compiler reports an error and the program does not run.

In this example, one program holds a search function constrained to Equatable and a largest function constrained to Comparable. The search finds bo at index 1 in its word array. The largest call compares 12, 7, and 20 and prints 20.

Note

A constraint narrows the types a generic function accepts, and in return it guarantees the operations the function needs.

Try it Yourself

Edit the code, then press Run.

Running is simulated

Exercise

Why does the compiler reject a call that passes Plain values to a function constrained to Equatable?