How to Store and Call Any Object Conforming to a Protocol with an Associated Type

I am pretty new to Swift, so I'm sure there is a clear way to do this that I'm missing. I have a generic LinkedListNode type that is then managed by a class MyLinkedList, which calls into another handler MyHandler. How can I define a Handler protocol such that MyLinkedList can take any handler conforming to the protocol? This is what I've got:

public class LinkedListNode<T> {
    var value: T
    var next: LinkedListNode?
    weak var previous: LinkedListNode?

    public init(value: T) {
        self.value = value
    }
}

protocol Handler {
    associatedtype S
    func handle(last: LinkedListNode<S>)
}

struct MyHandler<T: Encodable>: Handler {
    
    func handle(last: LinkedListNode<T>) {
        print(last)
        // TODO: Iterate through the linked list and encode the elements
    }
}
class MyLinkedList<U> {
    public typealias Node = LinkedListNode<U>
    private var _count: Int = 0
    public var head: Node?
    public var last: Node?
    private let handler: Handler // Error: Handler can only be used as a generic constraints becasue it has associated type requirements
    
    init(handler: Handler) { // Error: Handler can only be used as a generic constraints becasue it has associated type requirements
        self.handler = handler
    }
    
    func handleSomeStuff() {
        self.handler.handle(last: self.last!) // Error: Member handle can not be used on value of protocol type 'Handler'
    }
}

The trick is that I not only need T == S in my struct MyHandler, which conforms to Handler, but I also need a class MyLinkedList that can can store and call any object that conforms to Handler. In practice, all of these generic types are the same type: S == T == U.

The "Handler can only be used as a generic constraint..." error means that you need to make the type that uses Handler generic over it too. This will compile:

class MyLinkedList<U, H: Handler> where H.S == U {
    public typealias Node = LinkedListNode<U>
    private var _count: Int = 0
    public var head: Node?
    public var last: Node?
    private let handler: H
    
    init(handler: H) {
        self.handler = handler
    }
    
    func handleSomeStuff() {
        self.handler.handle(last: self.last!)
    }
}

note the where H.S == U portion as well, it will guarantee your restriction that S == T == U.

Albeit, if the only thing that Handler is going to require is the one and only one method handle(last:), you really don't need the protocol — you can simplify your design by using a function type:

class MyLinkedList<U> {
    public typealias Node = LinkedListNode<U>
    private var _count: Int = 0
    public var head: Node?
    public var last: Node?
    private let handler: (Node) -> ()
    
    init(handler: @escaping (Node) -> ()) {
        self.handler = handler
    }
    
    func handleSomeStuff() {
        self.handler(self.last!)
    }
}
1 Like

Ah, thank you. H: Handler> where H.S == U is what I was missing.

Naturally, in practice MyHandler has a lot of long-lived state and complexity I left out of the minimal example that warrants an object rather than a callback function.

Since you’re using == constraint with free variable on one side, you can also substitute the values:


class MyLinkedList<H: Handler> {
  typealias U = H.S
  ...
}

It may also be better to make Handler be agnostic from the concrete type LinkedList. Though it is understandably tricky since linked-list doesn’t really llay nice with Collection protocol, and you seem to need at least BidirectionalCollection to boot.

If Handler has only single method, consider replacing it with a closure:

class MyLinkedList<U> {
    public typealias Node = LinkedListNode<U>
    public typealias Handler = (Node) -> Void
    private var _count: Int = 0
    public var head: Node?
    public var last: Node?
    private let handler: Handler
    
    init(handler: Handler) {
        self.handler = handler
    }
    
    func handleSomeStuff() {
        self.handler(self.last!)
    }
}
Terms of Service

Privacy Policy

Cookie Policy