Opaque types, associatedtype and generic's for protocol requirements

I have an interesting problem which I can't explain why it's occurring, I have protocol Foo defined which includes an associated type and a function returning that:

protocol Foo {
    associatedtype Bar: View
    func makeBar(baz: String) -> Bar
}

struct SomeFoo: Foo {
    func makeBar(baz: String) -> some View {
        Text(String(describing: baz))
    }
}

This is fine, and it compiles, however soon as I add a generic type onto the function, it fails:

protocol Foo {
    associatedtype Bar: View
    func makeBar<A: StringProtocol>(baz: A) -> Bar
}

struct SomeFoo: Foo {
    func makeBar<A: StringProtocol>(baz: A) -> some View {
        Text(String(describing: baz))
    }
}

It seems like a strange corner-case which should be possible

The SwiftUI types are not necessary to understand the problem. We can reduce it a bit:

protocol Foo {
  associatedtype Bar
  func makeBar<A>(baz: A) -> Bar
}

struct SomeFoo: Foo { // error: type 'SomeFoo' does not conform to protocol 'Foo'
  func makeBar<A>(baz: A) -> some Any {
    String(describing: baz)
  }
}

Recall that the identity of an opaque return type is defined by the generic signature of the function which returns it. So makeBar<Int> returns one type, makeBar<String> conceptually returns a different type, makeBar<[Int]> conceptually returns yet another different type, etc.

It does not matter if the underlying types returned by makeBar<Int> and makeBar<String> end up being the same in practice, because that underlying type is opaque to callers. All the caller has is the function's generic context to use to assign identity.

That explains why the opaque result of makeBar cannot be used to witness the associated type declaration -- because it is not a single type; it depends on the generic parameter A of the function makeBar.

8 Likes

Ah, I suspected as much - this makes sense. This is one area which seems harder for me to grok _ I am getting there, appreciate it.

I am trying to figure out how best to create a ergonomic API for applying a style to a custom view component, aiming to mimic some of the same behaviours as components like Label with LabelStyle and LabelStyle.Configuration.Title/Icon - it seems without hacking the _makeView and _viewInputs it's very difficult to get close.

1 Like

I recently run into this limitation as well and it took me a while to remember this limitation. It would be great if the compiler could explain the issue a bit better.

5 Likes

You don't have to use opaque type. This works:

protocol BarProtocol {
}

struct Bar1: BarProtocol {
}

protocol Foo {
    associatedtype Bar: BarProtocol
    func makeBar<A>(baz: A) -> Bar
}

struct SomeFoo: Foo {
  func makeBar<A>(baz: A) -> Bar1 {
    Bar1()
  }
}

You can use this approach to achieve what you tried to do in your original SwiftUI example.

1 Like