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
}