I recently converted some of my code which originally used Type-Erasure to use the new Swift 5.7 any
existential.
However, I'm getting some issues when trying to use the any
keyword with already implemented generic types.
I'm running this on Xcode 14 Beta 2 (which has Implicitly Opened Existentials).
Here is an example:
protocol Provider<Value> {
associatedtype Value
func get() -> Value
}
struct S {
var stringProvider: any Provider<String>
}
Here is a very simple struct S
which has a member stringProvider
. I use the any
keyword here instead of making any Provider<String>
generic because I would like to be able to reassign stringProvider
to a different value later on (which has a different type).
struct ProviderView<P: Provider>: View {
let provider: P
var body: some View {
Text(String(describing: type(of: provider.get())))
}
}
Now here I have a ProviderView
SwiftUI struct, which takes in a Provider
and does stuff with it.
struct DummyProvider: Provider {
typealias Value = String
func get() -> String {
"Hello World!"
}
}
And this is just a dummy Provider
implementation which just returns a string.
This all works fine, the problem comes when I try to use ProviderView
with an existential any
.
struct ContentView: View {
let s = S(stringProvider: DummyProvider())
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
ProviderView(provider: s.stringProvider) // This is the erroring line
}
}
}
I get an error saying Type 'any Provider<String>' cannot conform to 'Provider'
.
I think I know why, it's because ProviderView
cannot have an any
existential as a generic argument.
My question is: Is there any way around this, without going back to type erasure? Am I doing something really badly wrong? Keep in mind that I need to be able to reassign S.stringProvider
to a Provider
of a different type.
CodePudding user response:
The problem here is that body
needs to be some specific type, so ProviderView needs to be some specific type (since body
relies on that type). It can't change at runtime. So ProviderView can't be generic over its Provider if that's going to change at runtime.
That means that ProviderView.provider
needs to be any Provider
, not generic:
struct ProviderView: View {
let provider: any Provider
var body: some View {
Text(String(describing: type(of: provider.get())))
}
}
So that part is expected.
The problem you'll run into is that the current runtime can't quite handle this yet, and you'll get an error later:
// Runtime support for parameterized protocol types is only available in iOS 99.0.0 or newer
ProviderView(provider: s.stringProvider)
I do expect this to improve in later betas, though I encourage you to open a Feedback to track it.
CodePudding user response:
any Provider
is an existential, and existentials being some kind of boxes, don't conform to the protocol inside the box, thus you see the error.
If you're not keen on having ProviderView
generic, you can add a custom initializer that uses some
- this represents the actual value conforming to the protocol, so the compiler is happy.
struct ProviderView: View {
let provider: any Provider
init(provider: some Provider) {
self.provider = provider
}