Getting Started With Swift — Generics

In the last post here, we looked at protocols and how they can be used to write more reusable code. Today we’re looking at something called Generics, a feature that helps write type-safe code whilst not knowing the actual types you’ll be using. For the impatient, here is the final gist of the code.

Why we need them

We’ll start with the same example as in Apple’s Swift language reference, swapping two variables.

func swapTwoInts(inout a:Int, inout _ b:Int) {
let temp = a
a = b
b = temp
}
var one = 1 
var two = 2
swapTwoInts(&one, &two)
print(one) // 2
print(two) // 1

We now have a simple function that takes two In-Out Int vars and swaps them over…but you’ve just been told you need to support swapping Doubles too.

You sigh and begin to type again.

func swapTwoDoubles(inout a:Double, inout _ b:Double) {
let temp = a
a = b
b = temp
}
var one = 1.0
var two = 2.0
swapTwoDoubles(&one, &two)
print(one) // 2.0
print(two) // 1.0

Now we have two functions, doing virtually the same thing — not good. Let’s refactor this to one method that uses generics instead.

func swapTwoValues<T>(inout a:T, inout _ b:T) {
let temp = a
a = b
b = temp
}
var one = "Hello"
var two = "World"
swapTwoValues(&one, &two)
print(one) // "World"
print(two) // "Hello"

Notice that the implementation of the function is exactly the same, only the function signature has changed.

First, we declare that we’ll be using a generic type called T. Then we simply use the T type in place of the types we were using. Second, we call the method in the same way. Here, the compiler can infer that T is equivalent to a String as that is the type of the variables we’re passing in. If say, two had still been an Int, the code wouldn’t compile.

Generics Types

The swapTwoValues function above is an example of a ‘generic function’. In fact it actually already exists in the Swift Standard library as swap. It doesn’t stop there though, hugely important parts of Swift are built on generics. The map and flatMap functions both use generics so that they can operate on any type of item, even optionals. Optionals though, are slightly different. Rather than being generic functions, an optional is a generic Type. And once again, the standard library is full of them. For example, the Array struct.

var numbers = [1, 2, 3, 4, 5]

Here we have an array of numbers called numbers and we have used type inference so that the compiler knows that it is an array. What this hides though is that it is not just an array — it is an array of Ints. Trying to add a string to this array would fail to compile. As a clearer example, here it is without type inference:

var numbers : [Int] = [1, 2, 3, 4, 5]
var numbers2 : Array<Int> = numbers

Let’s make a class for a Dog that only likes to eat one kind of food Dog<Food>.

class Dog<Food> {
func eat(food:Food) {
print("Nom nom nom")
}
}
class DogFood{}
let dog = Dog<DogFood>()
let dogfood = DogFood()
dog.eat(dogfood)

Pretty simple right? Only problem is that this leaves us pretty open to what foods our dog will accept. How do we ensure our dog will eat only suitable foods while still staying generic?

Type Constraints

Fortunately generics also come with the ability to constrain what types they can conform to. Lets create some protocols for suitable and unsuitable foods.

protocol Edible {}
protocol Meaty : Edible {}
protocol Veggie : Edible {}
class DogFood : Meaty {}
class Lettuce : Veggie {}
class Dog<Food:Meaty> {
func eat(food:Food) {
print("Nom nom nom")
}
}
class DogFood{}
let dog = Dog<DogFood>()
//let veggieDog = Dog<Lettuce>() // nope
let dogfood = DogFood()
let lettuce = Lettuce()
dog.eat(dogfood)
//dog.eat(lettuce) // no thanks

That’s better, now our Dogs can be instantiated with meaty foods only. Trying to create one that eats lettuce doesn’t work. Rabbits however, they love lettuce. We could create another generic type for Rabbit but then we’ll be repeating the logic for the eat() function. Protocols to the rescue!

Generic Protocols

Generics in protocols work slightly differently, there being two ways to make a protocol generic. The first we’ll look at is to use an abstract typealias.

protocol Eating { 
typealias FoodType:Edible
func eat(food:FoodType)
}
class Dog<Food:Meaty> : Eating {
func eat(food:Food) {
print("Nom nom nom")
}
}

Here we have a protocol called Eating that defines a single function called eat(). We have also declared that there will be a type called FoodType that must conform to Edible. Finally, we have added the Eating protocol to our Dog class.

What is interesting is that the compiler can infer that the FoodType type we declared in our protocol is of type Food. Let’s add our Rabbit class, moving our eat function up into the protocol.

protocol Eating { 
typealias FoodType:Edible
func eat(food:FoodType)
}
extension Eating {
func eat(food:FoodType) {
print("Nom nom nom")
}
}
class Dog<Food:Meaty> : Eating {}
class Rabbit<Food:Veggie> : Eating {}

Uh oh, this doesn’t compile. Because FoodType is abstract, i.e. it doesn’t have a declared value, the compiler no longer knows what to do with it. Fortunately, there is a simple solution.

class Dog<Food:Meaty> : Eating {
typealias FoodType = Food
}
class Rabbit<Food:Veggie> : Eating {
typealias FoodType = Food
}

We’re now declaring that the FoodType typealias equals Food and the compiler relaxes again.

The second way to make a generic protocol is by using the word Self inside the protocol to refer to the type that will ultimately be used, however, this only applies in certain situations.

protocol Cuddly { 
func findCuddlePartner() -> Self
}

We now have a protocol called Cuddly that declares a single function called findCuddlePartner(). The implementation of this function can only return types that match the current type, for example, if we extended Rabbit to be Cuddly, its findCuddlePartner function could only return other Rabbits.

This however, does not make a protocol generic. Why is that important? We’ll come to that in a second. For now, let’s add a second function called cuddle.

protocol Cuddly { 
func cuddle(cuddlee:Self)
func findCuddlePartner() -> Self
}

Our Cuddly protocol is now generic. Fortunately, the compiler can infer what Self is equal to as soon as you use it. The rules around using Self in protocols are as follows:

  • As a function return type: NOT generic
  • As a function parameter: Generic
  • As a variable type: Generic

Okay, so why does that matter?

Caveats

Generic protocols cannot be used in the same way that normal protocols can.

//let eater : Eating = Dog<DogFood>() // nope
//let eater : Eating<DogFood> = Dog<DogFood>() // still nope

Using Eating on its own is forbidden as the compiler doesn’t know what type FoodType will be. In this case, we can remove the declaration and use type inference, however, we couldn’t do that if we didn’t know the type beforehand.

For example, let’s say that you are pulling “eaters” from an uncontrolled source, maybe a remote web service or a random generator. You might receive a Dog, you might receive a Rabbit — what type is FoodType? We don’t know so how can the compiler?

At the time of writing, there is only one way “round this” which is to use a programming concept called a Thunk to provide a wrapper for types conforming to our generic protocol. I put that in quote marks because as you’ll see, it is only really useful for fairly simple logic.

class EatingThunk<Food:Edible> : Eating { 
private let _eat : (food:Food)->()
   init <Wrapped:Eating where Wrapped.FoodType == Food>(_ wrapped:Wrapped) {
_eat = wrapped.eat
}
   func eat(food:Food) {
_eat(food:Food)
}
}
let eater : EatingThunk<DogFood> = EatingThunk(dog)
eater.eat(dogFood)
//eater.eat(lettuce) // no thanks

Rather than using our Eating protocol, we now use our EatingThunk instead. This allows us to declare the FoodType in advance again, but as you can imagine, is very limiting. It would be nice if the compiler allowed us to defer that decision until the time that we put a value into the variable.

So how does it work?

The first thing to note is our class declares the Food generic type, as well as conforming to Eating itself. This means that it has an identical interface to the item it will wrap, enabling it to accept messages on its behalf before passing them on.

Second, we have a private constant called _eat. It will be a function with the same signature as the eat function from the Eating protocol. This will hold the eat function of our wrapped Eating, as you’ll see shortly.

Third is the init function. It is a generic function with more advanced type constraints than we’ve seen previously. It defines a type called Wrapped that must not only conform to the Eating protocol, but the type of it’s FoodType must be the same type as our EatingThunk’s Food type. It finally gets the eat function out of the wrapped item and stores it in our _eat variable, as mentioned above.

The last thing to note is inside our EatingThunk’s eat implementation, we simply call the function stored in _eat, passing on the arguments.

Wrapping Up

Generics are an incredibly important part of Swift’s type system, allowing us to write far more type-safe code whilst still staying fairly flexible. There are of course trade-offs as we saw with protocols, but often it is only a small price to pay.

Here is the final code. Have comments or your own examples to share? Let me know below.

In the next article, we’ll look at Enumerations.

Liked this article? Follow me on Twitter.

Show your support

Clapping shows how much you appreciated Sean Atkinson’s story.