Protocol with Associated Types or PATs is a powerful but largely misunderstood feature of Swift; it feels buggy but works as intended. The first encounter with PAT often terminates with the following dreaded error:
// Protocol 'MyProtocol' can only be used as a generic constraint because it has Self or associated type requirements
This error is by design and you're not doing anything wrong; it's just a small misunderstanding of how to use associated types or self requirement with protocols, which we'll hopefully explain and fix for now and the future.
Let's consider this popular example:
protocol Food {}
protocol Animal {
func eat(food: Food)
}
struct Cow: Animal {
func eat(food: Food) {}
}
We defined two protocols, Food and Animal, and added a requirement that Animal should eat food.
We also defined Cow as being Animal, which eats food—lots of food. While correct, the previous code doesn't convey that cows don't eat any kind of food. They prefer Grass.
Let's define Grass as a structure that conforms to Food:
struct Grass: Food {}
struct Cow: Animal {
func eat(food: Grass) {} // Type 'Cow' does not conform to protocol 'Animal'
}
Oops. Now that we've refined the Cow type to only eat Grass, we're not able to feed it anything else, which doesn't match with the requirement on Animal, that every animal should eat Food.
Associated types are there to solve this problem. We need to refactor the Animal type in order to let it work with an associated type, FoodType:
protocol Animal {
associatedtype FoodType: Food
func eat(food: FoodType)
}
An associated type can be thought about a generic type added on protocols. FoodType is a placeholder that will be determined by the class or structure implementing the protocol. We also conveyed that FoodType should conform to the Food protocol. Let's be realistic: animals don't eat String, Ints, or NotificationCenters.
With this, the Cow struct is now valid and properly compiles, and we can expressively convey that Lions eat Meat, and not any kind of food:
struct Meat: Food {}
struct Lion: Animal {
func eat(food: Meat) {}
}
This is great; now we can feed each animal, but can we feed them all? Let's see:
func feed(animal: Animal) { } // Protocol 'Animal' can only be used as a generic constraint because it has Self or associated type requirements
So we previously saw that protocols were types and could be used as types, unless they have Self or associated type requirements.
How can we solve this? The answer is, by using Animal as a generic constraint:
func feed<A: Animal>(animal: A) {
switch animal {
case let lion as Lion:
lion.eat(food: Meat())
case let cow as Cow:
cow.eat(food: Grass())
default:
print("I can't feed...")
break
}
}
feed(animal: Cow())
feed(animal: Lion())
The next thing you may want to do is to consider all of your animals in a single array:
var animals = [Animal]() // Protocol 'Animal' can only be used as a generic constraint because it has Self or associated type requirements
Nope, you can't store the animals altogether; you may want to experiment with a generic class that would hold all of your animals in an array:
class AnimalHolder<T> where T: Animal {
var animals = [T]()
}
let holder = AnimalHolder<# WHAT DO WE PUT HERE#>()
In both cases, we're hit by the limitation of PATs, which is that associated types are to be used as generic constraints and not as types.
Is it all lost? Not really; as Swift is an ever-evolving language, this limitation is well documented and you should always have it in the back of your head whenever you're programming with generics. For now, you have multiple options:
- Leverage type erasure pattern, which we'll see later
- Wait for generalized existentials (https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md#generalized-existentials), which will allow values to be protocols, even with associated types
- Refactor your code, and stick with object-oriented programming
As we're here to learn about Swift and how to work with Swift 5, let's learn the type erasure pattern and how we can use it to store arrays of animals (or any protocol with associated types or Self requirements).