You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: proposals/NNNN-existential-any.md
+59-15
Original file line number
Diff line number
Diff line change
@@ -10,6 +10,8 @@
10
10
11
11
Existential types in Swift have an extremely lightweight spelling: a plain protocol name in type context means an existential type. Over the years, this has risen to the level of **active harm** by causing confusion, leading programmers down the wrong path that often requires them to re-write code once they hit a fundamental [limitation of value-level abstraction](https://forums.swift.org/t/improving-the-ui-of-generics/22814#heading--limits-of-existentials). This proposal makes the impact of existential types explicit in the language by annotating such types with `any`.
12
12
13
+
Swift evolution discussion thread: [[Pitch] Introduce existential `any`](https://forums.swift.org/t/pitch-introduce-existential-any/53520).
14
+
13
15
## Motivation
14
16
15
17
Existential types in Swift have significant limitations and performance implications. Some of their limitations are missing language features, but many are fundamental to their type-erasing semantics. For example, given a protocol with associated type requirements, the existential type cannot conform to the protocol itself without a manual conformance implementation, because there is not an obvious concrete associated type that works for any value conforming to the protocol, as shown by the following example:
@@ -51,7 +53,7 @@ existential-type -> 'any' type
51
53
52
54
### Semantics of explicit existential types
53
55
54
-
The semantics of `any` types are the same as existential types today. Explicit `any` can only be applied to protocols and protocol compositions; `any` cannot be applied to nominal types, structural types, and type parameters:
56
+
The semantics of `any` types are the same as existential types today. Explicit `any` can only be applied to protocols and protocol compositions, or metatypes thereof; `any` cannot be applied to nominal types, structural types, type parameters, and protocol metatypes:
55
57
56
58
```swift
57
59
structS {}
@@ -61,35 +63,68 @@ let s: any S = S() // error: 'any' has no effect on concrete type 'S'
61
63
funcgeneric<T>(t: T) {
62
64
let x: any T = t // error: 'any' has no effect on type parameter 'T'
63
65
}
66
+
67
+
let f: any ((Int) ->Void) = generic // error: 'any' has no effect on concrete type '(Int) -> Void'
64
68
```
65
69
66
-
`any`also cannot be applied to `Any` and `AnyObject` (unless part of a protocol composition).
70
+
`any`is unnecessary for `Any` and `AnyObject` (unless part of a protocol composition):
67
71
72
+
```swift
73
+
structS {}
74
+
classC {}
68
75
69
-
> Rationale: `any Any` and `any AnyObject` are redundant. `Any` and `AnyObject` are already special types in the language, and their existence isn’t nearly as harmful as existential types for regular protocols because the type-erasing semantics is already explicit in the name.
76
+
let value: anyAny=S() // warning: 'any' is redundant on type 'Any'
77
+
let values: [anyAny] = [] // warning: 'any' is redundant on type 'Any'
78
+
let object: anyAnyObject=C() // warning: 'any' is redundant on type 'AnyObject'
70
79
80
+
protocolP {}
81
+
extensionC: P {}
71
82
72
-
The existential metatype becomes `(any P).Type`, and the protocol metatype remains `P.Protocol`.
83
+
let pObject: anyAnyObject& P =C() // okay
84
+
```
73
85
74
-
Examples
86
+
> **Rationale**: `any Any` and `any AnyObject` are redundant. `Any` and `AnyObject` are already special types in the language, and their existence isn’t nearly as harmful as existential types for regular protocols because the type-erasing semantics is already explicit in the name.
87
+
88
+
#### Metatypes
89
+
90
+
The existential metatype, i.e. `P.Type`, becomes `any P.Type`. The protocol metatype, i.e. `P.Protocol`, becomes `(any P).Type`. The protocol metatype value `P.self` becomes `(any P).self`:
75
91
76
92
```swift
77
93
protocolP {}
78
-
classC {}
94
+
structS: P {}
95
+
96
+
let existentialMetatype: any P.Type= S.self
97
+
98
+
protocolQ {}
99
+
extensionS: Q {}
100
+
101
+
let compositionMetatype: any (P & Q).Type= S.self
79
102
80
-
any P
81
-
anyAny// error
82
-
anyAnyObject// error
83
-
any P &AnyObject
84
-
any C // error
85
-
any P & C
86
-
any () ->Void// error
103
+
let protocolMetatype: (any P).Type= (any P).self
104
+
```
105
+
106
+
> **Rationale**: The existential metatype is spelled `any P.Type` because it's an existential type that is a generalization over metatypes. The protocol metatype is the singleton metatype of the existential type `any P` itself, i.e. `(any P).Type`. Under this model, the `any` keyword conceptually acts like an existential quantifier `∃ T`. `any P.Type` means `∃ T:P . T.Type`, i.e. the metatype of a concrete type conforming to `P`.`(any P).Type` is `(∃ T:P . T).Type`, i.e. the metatype of the existential type itself.
107
+
108
+
#### Type aliases and associated types
109
+
110
+
Like plain protocol names, a type alias to a protocol `P` can be used as both a generic constraint and an existential type. Because `any` is explicitly an existential type, a type alias to `any P` can only be used as an existential type, it cannot be used as a generic conformance constraint, and `any` does not need to be written at the use-site:
111
+
112
+
```swift
113
+
protocolP {}
114
+
typealiasAnotherP= P
115
+
typealiasAnyP=any P
87
116
88
-
(any P).Type
117
+
structS: P {}
89
118
90
-
functest<T>(_: T) where T ==any P
119
+
let p2: any AnotherP =S()
120
+
let p1: AnyP =S()
121
+
122
+
funcgeneric<T: AnotherP>(value: T) { ... }
123
+
funcgeneric<T: AnyP>(value: T) { ... } // error
91
124
```
92
125
126
+
Once the `any` spelling is required under the Swift 6 language mode, a typealias to a plain protocol name is not a valid type witness for an associated type requirement. Existential type witnesses must be explicit in the type alias with `any`.
127
+
93
128
## Source compatibility
94
129
95
130
Enforcing that existential types use the `any` keyword will require a source change. To ease the migration, I propose to start allowing existential types to be spelled with `any` with the Swift 5.6 compiler, and require existential types to be spelled with `any` under the Swift 6 language mode. The old existential type syntax will continue to be supported under the Swift 5 language mode, and the transition to the new syntax is mechanical, so it can be performed automatically by a migrator.
@@ -109,8 +144,17 @@ None.
109
144
110
145
## Alternatives considered
111
146
147
+
### Rename `Any` and `AnyObject`
148
+
112
149
Instead of leaving `Any` and `AnyObject` in their existing spelling, an alternative is to spell these types as `any Value` and `any Object`, respectively. Though this is more consistent with the rest of the proposal, this change would have an even bigger source compatibility impact. Given that `Any` and `AnyObject` aren’t as harmful as other existential types, changing the spelling isn’t worth the churn.
113
150
151
+
### Use `Any<P>` instead of `any P`
152
+
153
+
A common suggestion is to spell existential types with angle brackets on `Any`, e.g. `Any<Hashable>`. However, this syntax is misleading because it appears that `Any` is a generic type, which is confusing to the mental model for 2 reasons:
154
+
155
+
1. A generic type is something programmers can implement themselves. In reality, existential types are a built-in language feature that would be _very_ difficult to replicate with regular Swift code.
156
+
2. This syntax creates the misconception that the underlying concrete type is a generic argument to `Any` that is preserved statically in the existential type. The `P` in `Any<P>` looks like an implicit type parameter with a conformance requirement, but it's not; the underlying type conforming to `P` is erased at compile-time.
0 commit comments