andrewz andrewz - 20 days ago 5
Swift Question

Can you explain/solve these 'generic constraint' compiler errors?

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
}
}

Answer Source

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.