Protocol can only be used as a generic constraint because it has Self or associatedType requirements

89,708

Solution 1

Suppose for the moment we adjust your protocol to add a routine that uses the associated type:

public protocol RequestType: class {
    associatedtype Model
    var path: String { get set }
    
    func frobulateModel(aModel: Model)
}

And Swift were to let you create an array of RequestType the way you want to. I could pass an array of those request types into a function:

func handleQueueOfRequests(queue: [RequestType]) {
    // frobulate All The Things!

    for request in queue {
       request.frobulateModel(/* What do I put here? */)
    }
}

I get down to the point that I want to frobulate all the things, but I need to know what type of argument to pass into the call. Some of my RequestType entities could take a LegoModel, some could take a PlasticModel, and others could take a PeanutButterAndPeepsModel. Swift is not happy with the ambiguity so it will not let you declare a variable of a protocol that has an associated type.

At the same time it makes perfect sense to, for example, create an array of RequestType when we KNOW that all of them use the LegoModel. This seems reasonable, and it is, but you need some way to express that.

One way to do that is to create a class (or struct, or enum) that associates a real type with the abstract Model type name:

class LegoRequestType: RequestType {
  typealias Model = LegoModel

  // Implement protocol requirements here
}

Now it's entirely reasonable to declare an array of LegoRequestType because if we wanted to frobulate all of them we know we would have to pass in a LegoModel each time.

This nuance with Associated Types makes any protocol that uses them special. The Swift Standard Library has Protocols like this most notably Collection or Sequence.

To allow you to create an array of things that implement the Collection protocol or a set of things that implement the sequence protocol, the Standard Library employs a technique called "type-erasure" to create the struct types AnyCollection<T> or AnySequence<T>. The type-erasure technique is rather complex to explain in a Stack Overflow answer, but if you search the web there are lots of articles about it.

Swift 5.7 Existentials

Swift 5.7 introduces explicit existential using the any keyword. This will remove the "Protocol can only be used as a generic constraint…" error, but it doesn't solve the fundamental problem with this example. (Admittedly this example is academic, for demonstration purposes, and likely not useful in real code because of its limitations. But it also demonstrates how explicit existentials aren't a panacea.)

Here is the code sample using Swift 5.7 and the any keyword.

public protocol RequestType: AnyObject {
    associatedtype Model
    var path: String { get set }

    func frobulateModel(aModel: Model)
}

func handleQueueOfRequests(queue: [any RequestType]) {
    // frobulate All The Things!

    for request in queue {
       request.frobulateModel(/* What do I put here? */)
    }
}

Now our queue contains a collection of existentials and we no longer have the error about the "type cannot be used here because of Self or AssociatedType constraints". But it doesn't fix the underlying problem in this example because the frobulateModel method can still take an arbitrary type (the associated type of the entity conforming to the RequestType protocol).

Swift provides other mechanisms that can help compensate for this. In general you'd want constrain the Model value to expose behavior shared by all Models. The frobulateModel method might be made generic and have constraints on the parameter to follow that protocol. Or you could use Swift 5.7's primary associated types (SE-0346) to help constrain the behavior of Models at the protocol level.

So yes, explicit existentials can remove the error message that the OP asked about - but they are not a solution for every situation.

Also, keep in mind that existentials lead to indirection and that can introduce performance problems. In their WWDC session, Apple cautioned us to use them judiciously.

Solution 2

From Swift 5.1 - Xcode 11

You can use an opaque result type to achieve something like that.

imagine this:

protocol ProtocolA {
    associatedtype number
}

class ClassA: ProtocolA {
    typealias number = Double
}

So the following generates the error:

var objectA: ProtocolA = ClassA() /* Protocol can only be used as a generic constraint because it has Self or associatedType requirements */

But making the type opaque by adding the some keyword before the type will fix the issue and usually thats the only thing we want:

var objectA: some ProtocolA = ClassA()

Solution 3

Swift 5.1

An example how you can use generic protocols by implementing an associated type and base protocol:

import Foundation

protocol SelectOptionDataModelProtocolBase: class{}

protocol SelectOptionDataModelProtocol: SelectOptionDataModelProtocolBase {
    associatedtype T
    
    var options: Array<T> { get }
    
    var selectedIndex: Int { get set }
    
}

class SelectOptionDataModel<A>: SelectOptionDataModelProtocol {
    typealias T = A
    
    var options: Array<T>
    
    var selectedIndex: Int
    
    init(selectedIndex _selectedIndex: Int, options _options: Array<T>) {
        self.options = _options
        self.selectedIndex = _selectedIndex
    }
    
}

And an example View Controller:

import UIKit

struct Car {
    var name: String?
    var speed: Int?
}

class SelectOptionViewController: UIViewController {
    
    // MARK: - IB Outlets
    
    // MARK: - Properties
    
    var dataModel1: SelectOptionDataModelProtocolBase?
    var dataModel2: SelectOptionDataModelProtocolBase?
    var dataModel3: SelectOptionDataModelProtocolBase?

    // MARK: - Initialisation
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    convenience init() {
        self.init(title: "Settings ViewController")
    }
    
    init(title _title: String) {
        super.init(nibName: nil, bundle: nil)
        
        self.title = _title
        
        self.dataModel1 = SelectOptionDataModel<String>(selectedIndex: 0, options: ["option 1", "option 2", "option 3"])
        self.dataModel2 = SelectOptionDataModel<Int>(selectedIndex: 0, options: [1, 2, 3])
        self.dataModel3 = SelectOptionDataModel<Car>(selectedIndex: 0, options: [Car(name: "BMW", speed: 90), Car(name: "Toyota", speed: 60), Car(name: "Subaru", speed: 120)])

    }
    
    // MARK: - IB Actions
    
    
    // MARK: - View Life Cycle

    
}

Solution 4

A little change in design of your code could make it possible. Add an empty, non-associatedType, protocol at the top of your protocol hierarchy. Like this...

public protocol RequestTypeBase: class{}

public protocol RequestType: RequestTypeBase {

    associatedtype Model
    var path: Model? { get set } //Make it type of Model

}
public class RequestEventuallyQueue {

    static let requestEventuallyQueue = RequestEventuallyQueue()
    var queue = [RequestTypeBase]() //This has to be 'var' not 'let'

}

Another example, with classes derived from the protocol RequestType, making a queue and passing the queue to a function to print appropriate type

public class RequestA<AType>: RequestType{
   public typealias Model = AType
   public var path: AType?
}
public class RequestB<BType>: RequestType{
   public typealias Model = BType
   public var path: BType?
}

var queue = [RequestTypeBase]()

let aRequest: RequestA = RequestA<String>()
aRequest.path = "xyz://pathA"

queue.append(aRequest)

let bRequest: RequestB = RequestB<String>()
bRequest.path = "xyz://pathB"

queue.append(bRequest)

let bURLRequest: RequestB = RequestB<URL>()
bURLRequest.path = URL(string: "xyz://bURLPath")

queue.append(bURLRequest)

func showFailed(requests: [RequestTypeBase]){

    for request in requests{
        if let request = request as? RequestA<String>{
            print(request.path!)
        }else if let request = request as? RequestB<String>{
            print(request.path!)
        }else if let request = request as? RequestB<URL>{
            print(request.path!)
        }

    }
}

showFailed(requests: queue)

Solution 5

This error may also occur in the following scenario:

protocol MyProtocol {
    assosciatedtype SomeClass
    func myFunc() -> SomeClass
}

struct MyStuct {
    var myVar = MyProtocol
}

In this case, all you have to do to fix the issue is to use generics:

protocol MyProtocol {
    assosciatedtype SomeClass
    func myFunc() -> SomeClass
}

struct MyStuct<T: MyProtocol> {
    var myVar = T
}
Share:
89,708
Rahul Katariya
Author by

Rahul Katariya

Updated on September 17, 2020

Comments

  • Rahul Katariya
    Rahul Katariya almost 4 years

    I have a protocol RequestType and it has associatedType Model as below.

    public protocol RequestType: class {
    
        associatedtype Model
        var path: String { get set }
    
    }
    
    public extension RequestType {
    
        public func executeRequest(completionHandler: Result<Model, NSError> -> Void) {
            request.response(rootKeyPath: rootKeyPath) { [weak self] (response: Response<Model, NSError>) -> Void in
                completionHandler(response.result)
                guard let weakSelf = self else { return }
                if weakSelf.logging { debugPrint(response) }
            }
        }
    
    }
    

    Now I am trying to make a queue of all failed requests.

    public class RequestEventuallyQueue {
    
        static let requestEventuallyQueue = RequestEventuallyQueue()
        let queue = [RequestType]()
    
    }
    

    But I get the error on line let queue = [RequestType]() that Protocol RequestType can only be used as a generic constraint because it has Self or associatedType requirements.

  • Adolfo
    Adolfo over 7 years
    "your solution is very generic" 😂
  • Keab42
    Keab42 about 7 years
    This is one of the best explanations I've seen for this problem
  • Almas Adilbek
    Almas Adilbek over 5 years
    So GOOD explanation, so single answer.
  • Mofawaw
    Mofawaw almost 4 years
    What does frobulate mean?
  • Scott Thompson
    Scott Thompson almost 4 years
    In the 1980s there was a text adventure game series that began with the game Zork. In that game series there was the Frobozz Magic Company. They used to frobulate things. In short it is a silly phrase for an unspecific action.
  • ScottyBlades
    ScottyBlades almost 4 years
    This answer was a great excuse to frobulate usage of the word frobulate.
  • Andrea Leganza
    Andrea Leganza over 2 years
    Note: supported on iOS 13.0.0 or newer.
  • Michael Vescovo
    Michael Vescovo over 2 years
    @ScottThompson how do I test against the protocol rather than the implementation?