Mar 05, 2018

Swift Error: Protocol Can only Be Used as a Generic Constraint

Anyone working in Swift for any length of time eventually runs into the following error:

protocol 'XXX' can only be used as a generic constraint because it has Self or associated type requirements

It is probably the single biggest cause of confusion and frustration in Swift. If you're staring at this error and struggling to understand what it means, much less how to address it, don't worry. Pretty much everybody has this reaction. There are many ways you can run into this error, some more surprising than others.

Here are some scenarios that can cause this error, and how to address them all.

You can run into this on your own protocols created with an associated type:

protocol Container {
    associatedtype Element
    var values: [Element] { get set }
}

func takesAContainer(value: Container) { ... }

// error: protocol 'Container' can only be used as a generic constraint because it has Self or associated type requirements

You can run into this on your own protocols defined with Self in a signature:

protocol Combinable {
    func combineWith(other: Self) -> Self
}

func takesACombinable(value: Combinable) { }

//error: protocol 'Combinable' can only be used as a generic constraint because it has Self or associated type requirements

You can run into this from standard library protocols if they are defined with either of the patterns above. Equatable is a common culprit. This tends to catch people off guard, because you probably weren't thinking about Self requirements when you started using Equatable.

func takesAnEquatable(value: Equatable) { ... }

// error: protocol 'Equatable' can only be used as a generic constraint because it has Self or associated type requirements

You can run into this if your protocol inherits from any of the above:

protocol Viewable: Equatable {
    func display()
}

func takesAViewable(viewable: Viewable) { ... }

// error: protocol 'Viewable' can only be used as a generic constraint because it has Self or associated type requirements

Finally, you can run into this if your class composes with any of the above protocols:

class Person {
    var name: String
}

typealias EquatablePerson = Person & Equatable

func takesAnEquatablePerson(value: EquatablePerson) { }

// error: protocol 'EquatablePerson' can only be used as a generic constraint because it has Self or associated type requirements

For many of these errors, the solution is to make the functions generic and restrict the generic parameter to the protocol in question. Here are some examples:

func takesAnEquatable<T: Equatable>(value: T) { }
func takesAContainer<T: Container>(value: T) { }
func takesACombinable<T: Combinable>(value: T) { }
func takesAViewable<T: Viewable>(viewable: T) { }
func takesAnEquatablePerson<T: EquatablePerson>(value: T) { }

All these examples are functions, but you can use the same pattern on a struct or a class:

struct StructWithEquatables<T: Equatable> {
    var values: [T]
    func takesAnEquatable(value: T) { }
}

let swe = StructWithEquatables<Int>(values: [1, 2, 3])

There are still other situations where you will run into this problem. I'll cover them in a future article, along with another strategy on how to deal with it: type erasure. If you're interested, sign up on the sidebar and get notified when it comes out.