Constraints
Constraints
When you define a generic function or type, you can set constraints on the types that may be used. Constraints can be protocols or classes, and the syntax is <T: Protocol> or <T: Class>.
<T: Protocol>
The generic function swapValues<T> works with all types, but consider this generic function:
func max<T>(lhs: T, rhs: T) -> T {
return lhs > rhs ? lhs : rhs
}
This produces a compiler error informing you that T must conform to Comparable because the type T must be able to supply an answer to lhs > rhs ?:
func max<T: Comparable>(lhs: T, rhs: T) -> T {
return lhs > rhs ? lhs : rhs
}
Here’s a class you saw in Introduction to Protocol-Oriented Programming:
class FamilyCar: Vehicle { ... }
To follow along, open Starter/POPIntro.playground.
Instantiate a second instance of this class, then try to call max(lhs:rhs:):
let myCar = FamilyCar()
max(lhs: familyCar, rhs: myCar)
The compiler flags an error: Global function ‘max(lhs:rhs:)’ requires that ‘FamilyCar’ conform to ‘Comparable’. Actually, as soon as you type max, Xcode suggests code that uses the first Comparable property of familyCar:
max(lhs: familyCar.maxSpeed, rhs: myCar.maxSpeed)
<T: Class>
Another class in the same module is VehicleType, which has a move method:
class VehicleType {
...
func move(direction: Direction, duration: TimeInterval, speed: Double) { ... }
}
In your app, you might want to define a function that moves a person from locationA to locationB. You want to use the move method, and the person could use any VehicleType, so you set the constraint T: VehicleType:
func moveAtoB<T: VehicleType>(vehicle: T, locationA: CLLocation, locationB: CLLocation) { ... }
Note: This code is for illustration only. It’s not in the playground.
Where Clause
Use where when you need to set more than one constraint, for example, if T must inherit from the VehicleType class and also conform to the Comparable protocol:
<T where T: VehicleType, T: Comparable> // illustration only
And you’ve already seen this array initializer that creates an array containing the elements of a sequence S:
init<S>(_ s: S) where Element == S.Element, S : Sequence
You can also use where to constrain extensions of generic types. Return to Generics.playground and find extension Setting:
extension Setting {
mutating func save(from untypedValue: Any) {
if let value = untypedValue as? T {
self.value = value
}
}
}
If you add another save(from:) method to decode JSON data, you need a Decodable constraint:
extension Setting where T: Decodable {
mutating func save(from untypedValue: Any) {
if let value = untypedValue as? T {
self.value = value
}
}
mutating func save(from json: Data) throws {
let decoder = JSONDecoder()
self.value = try decoder.decode(T.self, from: json)
}
}
Or you can constrain only the save(from json:) method that requires the constraint:
extension Setting {
mutating func save(from untypedValue: Any) {
if let value = untypedValue as? T {
self.value = value
}
}
mutating func save(from json: Data) throws where T: Decodable {
let decoder = JSONDecoder()
self.value = try decoder.decode(T.self, from: json)
}
}
Here, the save(from json:) method doesn’t have a parameter of type T, so you must use where T: Decodable instead of save<T: Decodable>.
Those are the basics of generics. You’ll learn about two more topics in the next lesson.