Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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
    }
}
like image 804
andrewz Avatar asked Nov 18 '25 07:11

andrewz


1 Answers

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.

like image 132
Rob Napier Avatar answered Nov 20 '25 22:11

Rob Napier