Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't the Swift compiler infer the return type of a protocol with an associated item?

While reading the documentation on Opaque Types in the Swift Language Guide, near the very end is the snippet:

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

Container has an associated type, Item, and Array was extended to conform to Container. Why is the compiler lacking information to infer that this is an Array<T>?

The other confusing point is that with generic functions, they are written generally to use whichever types the client code specifies. So shouldn't the client code be the one to dictate what C is instead of the function imposing its return type of Array?

like image 252
Canucklesandwich Avatar asked Nov 01 '19 18:11

Canucklesandwich


1 Answers

This is how I explained opaque types to my team.

Your typical generic function, like the snippet you shared, may seem clear. Why wouldn't the following code be interpreted by the compiler as returning Array<T>?

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

The reason for the error is because of how C is used as a return. When you see something in the form -> C as a generic return, you're leaving the definition of that type up to the caller.

So assuming Array conforms as Container, then you would be able to do something like this:

let array: Array<Int> = makeProtocolContainer(Int(5))

Your callsite is defining what type C is.

But what if you want the function itself to define that? That's where opaque types come in.

In the example above, the makeProtocolContainer function is expected to return something generic that the caller can define. But the function itself is defining a concrete implementation that conforms to C, which is not the same as returning a generic type.

If you want the function itself to specify the type, then you just need to modify it with the new some keyword in Swift.

func makeProtocolContainer<T>(item: T) -> some Container {
    return [item]
}

Now, the function can return a concrete type. As a result, the caller can only know it as that opaque type, even if they might be equal.

/// Error: Cannot convert value of type 'some Container' to specified type 'Array<String>'
let container: Array<String> = makeProtocolContainer(item: "A")

You also can't use a generic type to hold to an opaque type.

/// Error: Protocol 'Container' can only be used as a generic constraint because it has Self or associated type requirements
let container: Container = makeProtocolContainer(item: 5)

But what you can do is either allow type inference or specify the opaque type yourself.

let container = makeProtocolContainer(item: 5)
// Same as
let container: some Container = makeProtocolContainer(item: 5)
like image 155
Josh Hrach Avatar answered Sep 28 '22 05:09

Josh Hrach