0

I'm running into what I think is a type-erasure issue that's described here. However, I'm having a hard time wrapping my head around the solve. There's some mention of "Opening Existentials" in this swift repository solving this here, but I'm not totally sure how to apply that in my situation.

Here's an example situation: I'm experimenting with the idea of a generic "getter" for fetching various services in my app. This will help clean up the interface and hopefully be a bit more scalable as more services get added. However, there are some issues that I described above.

protocol Service { }
protocol TestClient: Service {
    func fetchData() -> Data
}
class TestClientImpl: TestClient {
    func fetchData() -> Data {
        return Data()
    }
}

protocol SecondTestClient: Service {
    func fetchDifferentData() -> Data
}
class SecondTestClientImpl: SecondTestClient {
    func fetchDifferentData() -> Data {
        return Data()
    }
}

class ServiceFetcher {
    let services: [Service] = [TestClientImpl(), SecondTestClientImpl()]

    func getService<T: Service>() -> T? {
        for service in services {
            if let finalService = service as? T {
                return finalService
            }
        }
        return nil
    }
}

let serviceFetcher = ServiceFetcher()
let testClient: TestClient? = serviceFetcher.getService() // error: Type 'any TestClient' cannot conform to 'Service'

Any advice would be appreciated, thanks in advance!

johnny
  • 1,434
  • 1
  • 15
  • 26
  • 1
    The `where` condition in `getService` is redundant and the `return nil` line needs to be moved outside of the `for` loop. Apart from that I assume the problem is that `T` is considered to be a real type and not a protocol. – Joakim Danielson Feb 28 '23 at 16:21
  • @JoakimDanielson thanks, I updated the snippet with those fixes (I had them locally, just got clumsy when writing up this snippet). For your second part I'm not sure I fully understand -- how would I go about writing a generic getter in this case? Surely this is possible in Swift – johnny Feb 28 '23 at 16:29
  • 1. You've rewritten [`firstNonNil`](https://github.com/apple/swift-algorithms/blob/main/Sources/Algorithms/FirstNonNil.swift) 2. What you're doing can't be done directly; it requires https://forums.swift.org/t/typealias-conformance-to-protocols-why-cant-this-simple-thing-be-implemented/63302/16 –  Feb 28 '23 at 18:10
  • @Jessy this is just sample code, nothing final. Can you expand on point #2? I'm not sure how that link is related – johnny Mar 02 '23 at 21:10
  • 1
    Does this answer your question? [Protocol doesn't conform to itself?](https://stackoverflow.com/questions/33112559/protocol-doesnt-conform-to-itself) You should probably take this to the Swift forum. It’s **not** possible and they need to hear your use case. –  Mar 03 '23 at 00:25

1 Answers1

1

Swift provides no mechanism to make the constraint you want. You can do it, just not as type-constrainedly as you should be able to.

import Algorithms

extension ServiceFetcher {
  subscript<Service>(_: Service.Type = Service.self) -> Service? {
    services.firstNonNil { $0 as? Service }
  }
}
ServiceFetcher()[] as (any TestClient)?
ServiceFetcher()[any TestClient]