Protocol Can Only Be Used as a Generic Constraint Because It Has Self or Associatedtype Requirements

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

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.

Protocol can an only be used as a generic constraint because it has Self or associated type requirements

A little late, but I think it's still a good exercise to come up with a clean solution for your problem.

Within the Filterable protocol, you can't use the protocol type QueryParameters as a return type because QueryParameters has an associatedtype. Also, it's not really what you want, because returning a protocol type doesn't mean it conforms to itself (ie. QueryParameters type doesn't conform to QueryParameters).

Instead, what you need to do is create an associatedtype that conforms to the protocol QueryParameters to be used as a return type:

protocol Filterable {
associatedtype T : QueryParameters
func parameters() -> T
}

Then you can make Transactions conform to Filterable using the same function you have in your question but returning the opaque type some QueryParameters rather than the protocol type QueryParameters. This way you can return any type that conforms to QueryParameters which is what you really wanted to do:

struct Transactions: Filterable {
func parameters() -> some QueryParameters {
let transactionFilters = TransactionFilters(isWithdrawal: true)
return TransactionParameters(page: 1, filters: transactionFilters)
}
}

I hope you find that helpful. BTW, I fixed the typo in TransactionParameters in my answer :)

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

So I'm going to answer two questions

1) How are you supposed to get the associatedtype of a protocol?

There are two options. Option a: You are implementing the interface:

struct Resistor: ElectricallyDrawable {
typealias Line = Rectangle
}

In this case, it's implicitly clear that your line is a rectangle, since you define it.

Option b: You are extending the interface:

extension ElectricallyDrawable where Line == Rectangle {
//...
}

or

extension ElectricallyDrawable {
func x() {
if Line.self == Rectangle.self {
//...
}
}
}

So in either way, you simply 'get' the associatedtype by its name.

2) How would you actually do this?

Well, there are two options. You can either go the associatedtype way like so:

protocol ElectricallyDrawable {

associatedtype Line: Shape

func isConnected() -> Bool

func showPowerAnimation(using shape: Line)
}

Where you constrain yourself that the associatedtype (the actual type you're using) is a shape, or you add a generic to the function itself:

protocol ElectricallyDrawable {

func isConnected() -> Bool

func showPowerAnimation<S>(using shape: S) where S: Shape
}

either way is fine and the best approach probably depends on your use-case

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

When you added Hashable, that added Equatable, and that made this a Protocol with Associated Type (PAT) because Equatable is a PAT. A PAT is not a type; it's a tool for adding methods to other types.

You can't use a PAT as the type of a variable, you can't put it into an array, you can't pass it as a parameter directly, you can't return it as a value. The only point of a PAT is as a generic constraint (where L: Line). A PAT says what another concrete type must provide in order to be useable in some context.

How you should address this isn't clear. This doesn't really look like it should be a protocol at all. It depends on what code-reuse problem you're trying to solve here.

Protocols are generally about what something can do. Line seems like it's just trying to hide the implementation, without expressing anything; that's not a protocol. As written, there's no reason for generics or protocols here at all. What do the other implementations of Line look like? (I'm having trouble imagining how else you would possibly implement this type.)

I suspect the correct answer is to replace all of this with a Station struct and a Line struct. I'm not seeing where a protocol is pulling any weight.

Here's a way that I might implement what you're building, along with some new protocols to see what they're for. Some of this might be more than you'd need for this problem, but I wanted to show protocols in action.

// A protocol for "ID" types that automatically gives them handy inits
// Nested ID types mean that Line.ID can't get confused with Station.ID.
// The point of a protocol is to add additional features to a type like this.
protocol IDType: Hashable, ExpressibleByStringLiteral, CustomStringConvertible {
var value: String { get }
init(value: String)
}

extension IDType {
// For convenience
init(_ value: String) { self.init(value: value) }
// Default impl for ExpressibleByStringLiteral
init(stringLiteral value: String) { self.init(value: value) }
// Default impl for CustomStringConvertible
var description: String { return value }
}

struct Line: Equatable {
struct ID: IDType { let value: String }
let id: ID
let stations: [Station]
var origin: Station { return stations.first! } // We ensure during init that stations is non-empty
var terminus: Station { return stations.last! }

init(id: ID, origin: Station, stops: [Station], terminus: Station) {
self.id = id
self.stations = [origin] + stops + [terminus]
}
}

// Conforming Line to this protocol lets it print more beautifully.
extension Line: CustomStringConvertible {
var description: String { return "\(id): \(origin) -> \(terminus)" }
}

// Stations can't contain Line directly. These are value types, and that would be
// recursive. But this is nice because it lets us construct immutable Stations
// and then glue them together with Lines which may even be in independent
// systems (for example, the bus system might be separate from the rail system,
// but share stations)
struct Station: Hashable {
struct ID: IDType { let value: String }
let id: ID
let name: String

func lines(in system: System) -> [Line] {
return system.linesVisiting(station: self)
}
}

extension Station: CustomStringConvertible {
var description: String { return name }
}

struct System: Equatable {
let lines: [Line]

// Using Set here makes it clear there are no duplicates, and saves
// some hassle turning it back into an Array, but we could also just
// return Array here as Array(Set(...))
var stations: Set<Station> {
// Uniquify the stations
return Set(lines.flatMap { $0.stations })
}

func linesVisiting(station: Station) -> [Line] {
return lines.filter { $0.stations.contains(station) }
}
}

// Some examples of using it.
let stationNames = ["Shady Grove", "Bethesda", "Metro Center", "Glenmont",
"Wiehle-Reston East", "Largo Town Center"]

// Build up a few stations; obviously there are many more
let stations = Dictionary(uniqueKeysWithValues:
stationNames.map { ($0, Station(id: .init($0), name: $0)) })

// Define some lines
let redLine = Line(id: "OR",
origin: stations["Shady Grove"]!,
stops: [stations["Bethesda"]!, stations["Metro Center"]!],
terminus: stations["Glenmont"]!)

let silverLine = Line(id: "SV",
origin: stations["Wiehle-Reston East"]!,
stops: [stations["Metro Center"]!],
terminus: stations["Largo Town Center"]!)

// And glue them together into a system
let system = System(lines: [redLine, silverLine])

Swift - Protocol can only be used as a generic constraint because it has Self or associated type requirements

You have to turn these requirements around;

Instead of injecting a MicroServiceProvider into each request, you should write a generic MicroService 'Connector' Protocol that should define what it expects from each request, and what each request expects it to return.

You can then write a TestConnector which conforms to this protocol, so that you have complete control over how your requests are handled. The best part is, your requests won't even need to be modified.

Consider the following example:

protocol Request {
// What type data you expect to decode and return
associatedtype Response

// Turn all the data defined by your concrete type
// into a URLRequest that we can natively send out.
func makeURLRequest() -> URLRequest

// Once the URLRequest returns, decode its content
// if it succeeds, you have your actual response object
func decode(incomingData: Data?) -> Response?
}

protocol Connector {
// Take in any type conforming to Request,
// do whatever is needed to get back some potential data,
// and eventually call the handler with the expected response
func perform<T: Request>(request: T, handler: @escaping (T.Response?) -> Void)
}

These are essentially the bare minimum requirements to setup such a framework. In real life, you'll want more requirements from your Request protocol (such as ways to define the URL, request headers, request body, etc).

The best part is, you can write default implementations for your protocols. That removes a lot of boilerplate code! So for an actual Connector, you could do this:

extension Connector {
func perform<T: Request>(request: T, handler: @escaping (T.Response?) -> Void) {
// Use a native URLSession
let session = URLSession()

// Get our URLRequest
let urlRequest = request.makeURLRequest()

// define how our URLRequest is handled
let task = session.dataTask(with: urlRequest) { data, response, error in
// Try to decode our expected response object from the request's data
let responseObject = request.decode(incomingData: data)

// send back our potential object to the caller's completion block
handler(responseObject)
}

task.resume()
}
}

Now, with that, all you need to do is implement your ProfilePictureRequest like this (with extra example class variables):

struct ProfilePictureRequest: Request {
private let userID: String
private let useAuthentication: Bool

/// MARK: Conform to Request
typealias Response = UIImage

func makeURLRequest() -> URLRequest {
// get the url from somewhere
let url = YourEndpointProvider.profilePictureURL(byUserID: userID)

// use that URL to instantiate a native URLRequest
var urlRequest = URLRequest(url: url)

// example use: Set the http method
urlRequest.httpMethod = "GET"

// example use: Modify headers
if useAuthentication {
urlRequest.setValue(someAuthenticationToken.rawValue, forHTTPHeaderField: "Authorization")
}

// Once the configuration is done, return the urlRequest
return urlRequest
}

func decode(incomingData: Data?) -> Response? {
// make sure we actually have some data
guard let data = incomingData else { return nil }

// use UIImage's native data initializer.
return UIImage(data: data)
}
}

If you then want to send a profile picture request out, all you then need to do is (you'll need a concrete type that conforms to Connector, but since the Connector protocol has default implementations, that concrete type is mostly empty in this example: struct GenericConnector: Connector {}):

// Create an instance of your request with the arguments you desire
let request = ProfilePictureRequest(userID: "JohnDoe", useAuthentication: false)

// perform your request with the desired Connector
GenericConnector().perform(request) { image in
guard let image = image else { return }

// You have your image, you can now use that instance whichever way you'd like
ProfilePictureViewController.current.update(with: image)
}

And finally, to set up your TestConnector, all you need to do is:

struct TestConnector: Connector {

// define a convenience action for your tests
enum Behavior {
// The network call always fails
case alwaysFail

// The network call always succeeds with the given response
case alwaysSucceed(Any)
}

// configure this before each request you want to test
static var behavior: Behavior

func perform<T: Request>(request: T, handler: @escaping (T.Response?) -> Void) {
// since this is a test, you don't need to actually perform any network calls.
// just check what should be done
switch Self.behavior {
case alwaysFail:
handler(nil)

case alwaysSucceed(let response):
handler(response as! T)
}
}
}

With this, you can easily define Requests, how they should configure their URL actions and how they decode their own Response type, and you can easily write mocks for you connectors.

Of course, keep in mind that the examples given in this answer are quite limited in how they can be used. I would highly suggest you to take a look at this library I wrote. It extends this example in a much more structured way.

Swift: Protocol 'Set' can only be used as a generic constraint because it has Self or associated type requirements

You can define some Set-like protocol as follows:

public protocol MySet{
associatedtype Element

func add(elm : Element)
func remove(elm : Element) -> Bool
func clear()
func isMember(elm : Element) -> Bool
func size() -> Int
func isEmpty() -> Int
func isSubset(S2 : Self) -> Bool
func union(S2 : Self) -> Self?
func intersection(S2 : Self) -> Self?
func difference(S2 : Self) -> Self?
}

But I'm not sure it works as ADT as you expect.
As in the error message you get, in Swift, protocols with associated type (or having Self) can only be used as a generic constraint.

Swift protocols may not work like interface in some other Object-oriented languages in some cases.

You say this as practice, then this can be a good practice to learn how Swift protocols work.

What does Protocol ... can only be used as a generic constraint because it has Self or associated type requirements mean?

Protocol Observing inherits from protocol Hashable, which in turn inherits from protocol Equatable. Protocol Equatable has the following requirement:

func ==(lhs: Self, rhs: Self) -> Bool

And a protocol that contains Self somewhere inside it cannot be used anywhere except in a type constraint.

Here is a similar question.

Protocol inheritance with associated type

Once a protocol has an associated type, that protocol can't be used as a type by itself for instance declarations-- only for generic constraints and declaring conformance.

So in this case, Swift is saying "yeah, but what is the concrete type for StartRouterProtocol's associated type?"

In this case, it's asking you to either:

  1. Use a concrete type directly, i.e. let router: MyStartViewClass with this conformance declaration, elsewhere: class MyStartViewClass: StartRouterProtocol { ... })
  2. OR, push the need for a concrete type up one layer, as a generic constraint i.e.
class MyRouterController<T: StartRouterProtocol> {
let router: T
}

This is probably not what you were hoping for, but unfortunately associated types add complexity to how you use protocols, especially if you're familiar with generics & interfaces from other languages. (i.e. Java/C# interfaces)

You can work around some aspects of associated types by using a concept called "type erasure" -- but that can cause other problems and complexity.

Here's some further reading that may help: https://medium.com/monstar-lab-bangladesh-engineering/swift-from-protocol-to-associatedtype-then-type-erasure-a4093f6a2d08



Related Topics



Leave a reply



Submit