3

I get the following two compiler errors when trying to declare a protocol with an associatedType - not sure what a generic constraint is.

protocol Listener {
    associatedType ValueType
    func call(_ binding:Binding<ValueType>, value:ValueType)
}


class Binding<T> {
    var value:T?
    var listeners:[Listener] = [] // error 1: Protocol 'Listener' can only be used as a generic constraint because it has Self or associated type requirements
    func fire() {
        listeners.forEach { $0.call(self,value:self.value) } // error 2: Member 'call' cannot be used on value of protocol type 'Listener'; use a generic constraint instead
    }
}
user207421
  • 305,947
  • 44
  • 307
  • 483
andrewz
  • 4,729
  • 5
  • 49
  • 67
  • Possible duplicate of [Protocol can only be used as a generic constraint because it has Self or associatedType requirements](https://stackoverflow.com/questions/36348061/protocol-can-only-be-used-as-a-generic-constraint-because-it-has-self-or-associa) – Palle Sep 26 '17 at 18:45
  • Hm, I don't understand that answer. What could be done to my code to fix it? – andrewz Sep 26 '17 at 18:49
  • If a protocol has an associated type, you can't just type variables to it until Swift gains generalized existentials, a feature that's been on the table for a long time but still isn't present in Swift 4. Maybe it will be added in Swift 5, Swift 6, Swift 7, or perhaps Swift 13 ("The Lucky One"). In the meantime, you can add a second generic parameter to your `Binding` class which is constrained to conform to `Listener` and type the array to contain the generic parameterized type. – Charles Srstka Sep 26 '17 at 19:05
  • 1
    Why don't you show me by writing an answer? – andrewz Sep 26 '17 at 19:06

1 Answers1

12

This is an incorrect use of a protocol. Protocols associated types are determined by the implementer of the protocol, not the user of the protocol. Generics are determined by the user of the protocol. There is no automatic wrapping of protocols into generics (that feature will be called "existential containers" and we don't know when it will come if ever).

[Listener] is not a complete type. what is the ValueType?

For this particular case, there is no reason for a protocol. It captures exactly one function. You should just pass the function:

class Binding<T> {
    var value: T?
    var listeners:[(Binding<T>, T) -> ()] = []
    func fire() {
        if let value = value {
            listeners.forEach { $0(self, value) }
        }
    }

    init() {}
}

If you really need the protocol, you can lift it into a type eraser (AnyListener):

protocol Listener {
    associatedtype ValueType
    func call(_ binding:Binding<ValueType>, value:ValueType)
}

struct AnyListener<ValueType>: Listener {
    let _call: (Binding<ValueType>, ValueType) -> ()
    init<L: Listener>(_ listener: L) where L.ValueType == ValueType {
        _call = listener.call
    }
    func call(_ binding: Binding<ValueType>, value: ValueType) {
        _call(binding, value)
    }
}

class Binding<T> {
    var value:T?
    var listeners: [AnyListener<T>] = []
    func fire() {
        if let value = value {
            listeners.forEach { $0.call(self, value: value) }
        }
    }
}

let listener = ... some listener ....
binding.listeners.append(AnyListener(listener))

Or you could just make AnyListener into a more concrete struct and get rid of the protocol. But for such a simple case I'd pass the function usually.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610