Chapter 19 Generics
Up to now, when we’ve defined a function we’ve always provided a specific type for its arguments and return type. Generics are a way of defining functions or types that can work with multiple different data types.
Generics are used heavily throughout Swift itself. For example the Array
is defined as a generic type.
19.1 Generic Functions
You define a generic function by using angle brackets with a type placeholder after the function name. The convention is for this placeholder to be called T
.
func doNothing<T> (x:T) -> T {
return x
}
doNothing(1) // 1
doNothing("hello") // "hello"
In this example we define a function that does nothing except return the parameter that is passed to it. By adding <T>
after the function name we’ve indicated that this is a generic function and that used T
as a placeholder for the type. In the functions type signature we have indicated that it takes a parameter called x
of type T
and returns something of type T
.
We can call this function with an integer or a string and it works with either one.
19.2 Generic Types
Arrays in swift are generic – you can use them with any type. You can also define classes, structs or enumerations that will work with any type.
struct LifoQueue<T> {
var items = [T]()
mutating func enqueue(element:T){
items.append(element)
}
mutating func dequeue() -> T{
return items.removeAtIndex(0)
}
}
var q = LifoQueue<Int>() // {0 elements}
q.enqueue(2) // {[2]}
q.enqueue(3) // {[2, 3]}
q.enqueue(4) // {[2, 3, 4]}
q.dequeue() // 2
q.dequeue() // 3
q.dequeue() // 4
var q2 = LifoQueue<String>() // {0 elements}
q2.enqueue("Hello") // {["Hello"]}
q2.enqueue("I") // {["Hello", "I"]}
q2.dequeue() // "Hello"
In this example we define a generic data structure that implements a Last-In-First-Out queue. Similar to our generic function we indicate that the structure is generic by adding <T>
after the name of the struct and then we can use T
to refer to the type in the structs definitions. So for this example we declare an array of type T
at line 3, an enqueue function that takes an element of type T
at line 5 and a function that returns an element of type T
at line 9.
19.3 Type constraints
We can constrain the types that are allowed to be used in a generic either by class or by protocol.
Lets say we only wanted our Queue to accept numeric types. We could define a numeric protocol and say that Int
, Float
, and Double
conform to it. Then we can define a generic queue that only accepts types that conform to this protocol.
protocol Numeric { }
extension Float: Numeric {}
extension Double: Numeric {}
extension Int: Numeric {}
struct ConstrainedLifoQueue<T: Numeric> {
var items = [T]()
mutating func enqueue(element:T){
items.append(element)
}
mutating func dequeue() -> T{
return items.removeAtIndex(0)
}
}
var q3 = ConstrainedLifoQueue<Float>() // Ok
var q4 = ConstrainedLifoQueue<String>() // Error