Heh, perhaps the (any P).Type
win was not as clear as I initially thought. Between this proposal, @jrose's suggestion that any P.Type
and (any P).Type
be the same, and @Joe_Groff's earlier suggestion that P.Protocol
become (any P).Type
, we have three different (and incompatible) ideas of how any
syntax should apply to protocol metatypes. IMO, having P.Type
become (any P).Type
makes sense, since to me that syntax naturally reads as "the metatype of all types which conform to P
", and I'm ambivalent-to-negative on having any P.Type
mean the same thing. I think I big plus of (any P).Type
is that it puts some visual distance and a bit of syntactic clarification between the former P.Type
and P.Protocol
. If we continued to allow P.Type
as a syntactic form (even if it were required to prefix with any
, we'd lose that benefit.
In the same vein, I'd be strongly against allowing both any P.Type
and (any P).Type
and having those mean different things. I suspect that would lead to even more confusion than the Type
-Protocol
issue we have today.
On another note, I'd like to discuss a bit more about the source break here. As I mentioned previously, this is going to be a big break if adopted as is. Hopefully it will be as simple as prepending any
to all existing existential type names, but I do wonder whether there are ways that we could mitigate the amount of churn that will be required.
As came up in a previous thread, some uses of existentials are isomorphic to generics thanks to Swift not requiring monomorphization for generic functions. That is, if we remove the associated type from the proposal's example, there's not a fundamental issue with:
protocol P {
func test()
}
func generic<ConcreteP: P>(p: ConcreteP) {
p.test()
}
func useExistential(p: P) {
generic(p: p)
}
In fact, we can already call generic
from within useExistential
today, with _openExistential
:
func useExistential(p: P) {
_openExistential(p) { concrete in
generic(p: concrete)
}
}
I suspect that we could come up with a reasonable list of non-problematic uses of existentials that maybe don't need to require any
syntax. In the above situation, we could auto-open the existential so that the generic(p: p)
call succeeds without any dance.
The proposal also calls out concerns that "the type of value stored can change dynamically" but this is only true insofar as the value can change at all—if the value of existential type is a let
(as with non-inout
function parameters), then we needn't worry about the concrete type changing dynamically.
I think we could possibly also get away with allowing bare existentials for @objc
protocols.
Together with auto-opening of existentials, I do wonder if a rule like the following would get us most (all?) of the benefit while reducing the code churn required:
An existential type must be annotated with
any
if the protocol is not@objc
and either of the following is true:
- The protocol has
Self
orassociatedtype
requirements- The value of existential type is mutable
Since SE-0306 hasn't shipped with an official version of Swift yet (right?), that would allow us to only require updating the uses of mutable values of existential type (which are not @objc
). This would likely include any Swift protocol delegates, since most of those will be weak var
s, but it might carve out enough exceptions to make the source break more palatable.
Of course, I have a generally higher tolerance for source breaks, so if this problem is deemed big enough for the big, hard break in Swift 6, then I'm all for it. I'd rather end up in a world with fewer complex rules for type syntax, so a rule of "existential types are prefixed with any
" is preferable to me in that regard. Just wanted to raise a couple of alternative paths forward in the event that the widest-reaching version of this proposal is unpalatable to some.