[Pitch] Implicitly opening existentials

I'm excited to see this, opening existentials is a major gap in Swift expressiveness.


I'm a bit concerned about the need to repeat the protocol name in some P. It does not look that bad in proposal with single-letter names, but I'm afraid that in production code it may be pretty verbose:

func authenticate(alternativeAuthenticationProviders: [any AlternativeAuthenticationProvider]) {
    for provider: some AlternativeAuthenticationProvider in alternativeAuthenticationProvider {
        ...
    }
}

Would it be possible to combine with proposal with SE-0315 to be able to use some _ when we want to open the existential, but don't want to change the type otherwise?

func authenticate(alternativeAuthenticationProviders: [any AlternativeAuthenticationProvider]) {
    for provider: some _ in alternativeAuthenticationProvider {
        ...
    }
}

I'm a bit paranoid about lack of name for the opened type when opening using some P. I would feel safer having something like let <T: P> openedX = x in my toolbox in addition to the proposed mechanism. But as a workaround, one can extract code into a generic function. So I guess it's not really an issue.


func cannotOpen7<T: P>(_ value: T) -> X<T> { /*...*/ }

For now, we could allow opening in this case, but erase result to Any. Or better to an existential for all protocols which X conforms to:

protocol P {}
protocol Q {}
protocol R {}
struct X<T: P>: Q, R {}
func cannotOpen7<T: P>(_ value: T) -> X<T> { /*...*/ }
let x: any P = ...
let y = cannotOpen7(x) // type of y is any Q & R

I've had cases like this in production, I think it has value. But if it is not supported, there is a workaround:

func cannotOpen7Boxed<T: P>(_ value: T) -> any Q & R {
    return cannotOpen7(value)
}
let y = cannotOpen7Boxed(x)

To fully support all cases of erasure after opening we would need generalised existentials which can box arbitrary complex generic type. Then this case would be type-erased to any<T: P> X<T>. And similarly result of

func decomposeQ<T: Q>(_ value: T) -> (T, T.B, T.B.A) {}

would erase to any<T: Q> (T, T.B, T.B.A).

If we get generalised existentials in the future, would changing erased type from (any Q, any P, Any) to any<T: Q> (T, T.B, T.B.A) be a source-breaking change?


I think there is a typo here - overloaded1 has two arguments, but is called with one:

protocol P { }

func overloaded1<T: P, U>(_: T, _: U) { } // A
func overloaded1<U>(_: Any, _: U) { }     // B

func changeInResolution(p: any P) {
  overloaded1(p) // used to choose B, will choose A with this proposal
}

I suspect some people will try to open existential in return statement. I don't think anything can be done to improve this, other than provide an informative error message:

func noOpeningInReturn(_ x: any P) -> some P {
    let y: some P = x // works
    return x // Does not work
    return y // Does not work either
}
1 Like