Generalized opaque and existential type constraints

Okay, I was just thinking in terms of function declarations, but this helped me understand that there's a larger picture here. If I understand correctly, there's a desire to build a syntax for declaring constraints on opaque and existential types throughout the language. For the sake of making sure I understand that desire, I'd like to sketch out a few scenarios to make sure I understand.

(I'll use the some Collection C syntax as a strawman for now, acknowledging that arguments have been made against it, but I need to use some syntax to illustrate the ideas.)

// Given this:
protocol Strawman {
  associatedtype Input
}

We need a syntax that can be used in all of the following scenarios:

  • Function parameters

    func example1(_ x: some Strawman A)
      where A.Input == Int
    
    func example2(_ x: any Strawman A)
      where A.Input == Int
    
  • Return types

    func example3() -> some Strawman A  
      where A.Input == Int
    
    func example4() -> any Strawman A  
      where A.Input == Int
    
  • Stored properties

    struct Example5 {
      var x: any Strawman A where A.Input == Int
    }
    
    struct Example6 {
      var x: some Strawman A where A.Input == Int
    }
    

    (There was some question whether supporting Example6 is a desirable feature; see Holly's post here. I'm just showing how it might be spelled if we did want to support it.)

  • Structural return types

    func example7() -> (some Strawman A, some Strawman B)
      where A.Input == B.Input 
    
    func example8() -> (any Strawman A, any Strawman B)
      where A.Input == B.Input
    
  • Are there other cases I missed?

As mentioned before, the goal of any syntax proposed here is not to replace the existing <T> syntax; rather, we're just trying to find a syntax that allows us to put constraints on some and any types. Some of the above examples could be written today using regular generics (specifically, examples 1 and maybe 6), but most of the above are currently inexpressible. Likewise, it is not a requirement that everything you can do with generics be expressible via constraints on opaque types.

I'll also note that the "primary associated types" proposal could simplify many of the examples here, but does not replace the need for a general syntax, because a protocol can have many associated types which are not a primary associated type.


With all of that written out, I'd like some clarification. Many of the suggestions earlier in this thread talk only about opaque result types, but those don't seem like they'd work with existential types. For example, using the "angle brackets after the arrow" syntax, how would you write something that returns a constrained existential type, like example4 above?

func whatDoWeDoHere() -> <T> any Strawman where ???

Likewise, what would a constrained existential member look like, especially in the case where there may be a naming conflict with an external struct?

struct S<Input> {
  var x: any Strawman A where A.Input == Input

  // If we don't support naming `any Strawman`, what can you
  // reference in a where clause?
  var y: any Strawman where ???
}

It's not clear to me that any other syntax has been proposed which can handle all the use cases suggested above.

1 Like