Opaque types

Hello!
There is a statement in the documentation Opaque types:

You also can’t use it as constraint in a generic return type because there isn’t enough information outside the function body to infer what the generic type needs to be.

// Error: Not enough information to infer C.
func makeProtocolContainer<T, C: Container>(item: T) -> C {
    return [item]
}

First of all I could't understand it, also using compiler from Xcode 13.2.1 it force to cast it to C.

Could somebody please explain it?

Thanks

The function returns Array<T>, so the only thing the compiler could possibly infer is that C == Array<T>. That would make the function no longer generic over C, since you could just type it as func makeProtocolContainer<T> -> Array<T>.

You can’t understand it probably because the explanation isn’t correct. If you could file a bug on the Swift book GitHub repository, it can be tracked for fixing later (cc @amartini). The caller of such a function can absolutely specify C at the call site.

The problem here with the function declared that way is that it’s most probably not actually what you want to express.

When you have a generic parameter in angle brackets, you’re saying that the caller gets to decide what that type should be. This means in this case that the function here is supposed to give a caller back a result value of type C, a Container type of the caller’s choosing.

But the implementation only ever returns an array. That’s not good enough: a caller could choose C to be something other than Array. They could also choose an Array<String> but provide an item that’s an Int. This function declares that it can provide a return value for those scenarios but it actually doesn’t at all.

In allowing you to express what you’d want here as the function declaration author, opaque types work like “reverse generics”: in other words, when you write that the function returns some Container, you’re saying that the underlying concrete type will be of the callee’s (not caller’s) choosing. In this implementation, the callee always chooses Array<T> but it can reserve that knowledge as an implementation detail for itself. The caller then has to be prepared to handle a return value of any type that conforms to that protocol.

4 Likes
makeProtocolContainer(item: "🫔") as Array

still wouldn't compile though, without this further constraint:

func makeProtocolContainer<T, C: Container & ExpressibleByArrayLiteral>(item: T) -> C
where C.ArrayLiteralElement == T {
  [item]
}

Of course it wouldn’t.

The point, though, is that the explanation as to why isn’t correct at all: “You also can’t use it as constraint in a generic return type because there isn’t enough information outside the function body to infer what the generic type needs to be.”

You can use a generic return type in this way, and the caller can provide enough information from outside the function body to infer the generic type precisely.

The function implementation doesn’t support what such a function declaration promises, or put another way the declaration doesn’t express the constraints that the implementer intends to express, but that is not what the explanation reads. No wonder it’s confusing.

thank you very much for the explanation @xwu , now I understand that the statement is incorrect, but I got your point!

I think you meant to tag @Alex_Martini

1 Like