Opaque result types

'Existential' is from predicate logic. It's a statement that says that a thing exists.

So this Swift code:

protocol P {}
func existential() -> P

Will return a thing that conforms to P. It can be any sort of P (any concrete type at all that conforms to P). In a way, it's proof that an object can be made that is a P - thus the tie in to the idea from logic.

The code above will compile and run and work fine, but if your protocol P has an associatedtype in it, you'll get an error message instead because of limitations in the compiler and because there are semantic difficulties with figuring out how you'd actually work with such a thing when you don't necessarily know what the associated type(s) are. Working out these issues has been discussed as "generalizing existentials", thus the comments you'll see here on this forum about how everything will be sunshine and roses as soon as we get "generalized existentials".

Where returning an existential is logically saying "here exists one", a generic function is saying "all":

func generic<T>(t: T) -> T { return t }

For all types T, if you give me a t of that type, I'll return you something of that type. And, of course, with where clauses you can restrict that to some subset of types, but with generics the idea remains that if you have <T: Collection> and then -> T that I'm saying that this will work with all Collections.

Existentials: Writer of the function decides what the concrete type is, and it can be different every time. Caller just knows it is a thing that exists that is a P.
Generics: Writer of the function makes it work for all (of some subset of) types. Caller picks which particular type for each call.

Now these opaque types are like existentials in that the writer of the function decides what the concrete type is.
The difference is that it must be the same type every time.

And also these opaque types are a bit like responsibility-flipped generics, where the function writer picks the type, and the caller has the generic-esque responsibility of dealing with all (of some subset of) returned types.

And the result is that for a certain class of problem, you get most of the benefit you'd get from generalized existentials, but the implementation complexity and issues are much easier to reason about by making the rule 'one type and always the same type' instead of 'any possible existing type'.

30 Likes