SE-0335: Introduce existential `any`

I can shed a little light on the complaints/questions that I've seen very frequently as someone working on Swift's diagnostics.

This specific error message is a huge source of confusion, not only for beginners, but also for experienced Swift programmers:

protocol P { ... }

func generic<T: P>(value: T) { ... }

func testExistential(p: P) {
  generic(value: p) // error: Protocol 'P' as a type cannot conform to the protocol itself
}

This is a super contrived example, but people hit this error message all the time in a variety of ways, and the message doesn't match people's understanding of protocols. Their understanding is something along the lines of - when you use P as a type, you know you have a value that conforms to the protocol, so why does P not conform to the protocol?

There are potential language features that would allow the above example to compile -- by implicitly opening the existential to produce an opaque type, for example -- but there are other ways to hit this fundamental limitation that don't have a language feature to resolve it. Here's another example with the error message on main where SE-0309 is implemented:

import SwiftUI

struct MyView: View {
  var body: View { // error: Cannot infer 'Body' = 'View' because 'View' as a type cannot conform to protocols; did you mean to use an opaque result type?
    Text("")
  }
}

The programmer probably meant to use an opaque type here (which I why I recently added a fix-it to insert some). The only time you need an existential is when you want to conditionally return different underlying types in the implementation. To me, this is a strong indicator that some should be the default here, and programmers should opt into the dynamism provided by existential types only when they need to.

The family of errors that I anticipate becoming a big source of confusion after the acceptance of SE-0309 are the ones about API from the protocol not being available on the existential type due to associated types being erased. Here's an example, compiled with main where SE-0309 is implemented:

let collection: Collection = [1, 2, 3]

collection.index(after: collection.startIndex) // error: Member 'index' cannot be used on value of protocol type 'Collection'; use a generic constraint instead

I think this will really exacerbate people's confusion because it's very difficult to reason about what this error message is saying. When I use Collection as a type, I know that I have a value that conforms to Collection. This means that value has an implementation for all of the requirements on the Collection protocol. This API is a requirement, so I know that the underlying type must implement it. Why can't I call it???

I don't think this is just a problem with the error messages. Ignoring the fact that it requires several paragraphs to explain what's going on (which definitely can't fit in a single-line error message :slightly_smiling_face: ), when I explain this to folks, one of the most common responses I get is "wait, I thought I was using a generic constraint". And to be fair, that mental model isn't entirely wrong -- the protocol is a conformance requirement on the value that the existential type stores. But there is no indication at all that using the protocol "as a type" is any different from using it as a conformance requirement in a generic signature.

Obviously my experience only provides anecdotal evidence, but I've seen some pretty strong anecdotal evidence that the conflation between these concepts in the syntax has caused programmers to conflate these two concepts in their mental model, and it's caused a lot of frustration in the generic programming experience.

I believe that some Collection and any Collection will greatly help folks understand that there is a difference between constraints on opaque types and constraints on existential types, especially by giving us terminology to discuss these different concepts. "Collection as a type" has proven to be unhelpful, and using some kind of modifier in the terminology like "any Collection" doesn't make a lot of sense considering what you actually write in the source language to express "any Collection".

24 Likes