|
| 1 | +# Introduce existential `any` |
| 2 | + |
| 3 | +* Proposal: [SE-NNNN](NNNN-existential-any.md) |
| 4 | +* Authors: [Holly Borla](https://github.com/hborla) |
| 5 | +* Review Manager: TBD |
| 6 | +* Status: **Awaiting implementation** |
| 7 | +* Implementation: [apple/swift#40282](https://github.com/apple/swift/pull/40282) |
| 8 | + |
| 9 | +## Contents |
| 10 | + - [Introduction](#introduction) |
| 11 | + - [Motivation](#motivation) |
| 12 | + - [Proposed solution](#proposed-solution) |
| 13 | + - [Detailed design](#detailed-design) |
| 14 | + - [Grammar of explicit existential types](#grammar-of-explicit-existential-types) |
| 15 | + - [Semantics of explicit existential types](#semantics-of-explicit-existential-types) |
| 16 | + - [`Any` and `AnyObject`](#any-and-anyobject) |
| 17 | + - [Metatypes](#metatypes) |
| 18 | + - [Type aliases and associated types](#type-aliases-and-associated-types) |
| 19 | + - [Source compatibility](#source-compatibility) |
| 20 | + - [Effect on ABI stability](#effect-on-abi-stability) |
| 21 | + - [Effect on API resilience](#effect-on-api-resilience) |
| 22 | + - [Alternatives considered](#alternatives-considered) |
| 23 | + - [Rename `Any` and `AnyObject`](#rename-any-and-anyobject) |
| 24 | + - [Use `Any<P>` instead of `any P`](#use-anyp-instead-of-any-p) |
| 25 | + - [Future Directions](#future-directions) |
| 26 | + - [Extending existential types](#extending-existential-types) |
| 27 | + - [Re-purposing the plain protocol name](#re-purposing-the-plain-protocol-name) |
| 28 | + - [Revisions](#revisions) |
| 29 | + - [Changes from the pitch discussion](#changes-from-the-pitch-discussion) |
| 30 | + - [Acknowledgments](#acknowledgments) |
| 31 | + |
| 32 | +## Introduction |
| 33 | + |
| 34 | +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`. |
| 35 | + |
| 36 | +Swift evolution discussion thread: [[Pitch] Introduce existential `any`](https://forums.swift.org/t/pitch-introduce-existential-any/53520). |
| 37 | + |
| 38 | +## Motivation |
| 39 | + |
| 40 | +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: |
| 41 | + |
| 42 | +```swift |
| 43 | +protocol P { |
| 44 | + associatedtype A |
| 45 | + func test(a: A) |
| 46 | +} |
| 47 | + |
| 48 | +func generic<ConcreteP: P>(p: ConcreteP, value: ConcreteP.A) { |
| 49 | + p.test(a: value) |
| 50 | +} |
| 51 | + |
| 52 | +func useExistential(p: P) { |
| 53 | + generic(p: p, value: ???) // what type of value would P.A be?? |
| 54 | +} |
| 55 | +``` |
| 56 | + |
| 57 | +Existential types are also significantly more expensive than using concrete types. Because they can store any value whose type conforms to the protocol, and the type of value stored can change dynamically, existential types require dynamic memory unless the value is small enough to fit within an inline 3-word buffer. In addition to heap allocation and reference counting, code using existential types incurs pointer indirection and dynamic method dispatch that cannot be optimized away. |
| 58 | + |
| 59 | +Despite these significant and often undesirable implications, existential types have a minimal spelling. Syntactically, the cost of using one is hidden, and the similar spelling to generic constraints has caused many programmers to confuse existential types with generics. In reality, the need for the dynamism they provided is relatively rare compared to the need for generics, but the language makes existential types too easy to reach for, especially by mistake. The cost of using existential types should not be hidden, and programmers should explicitly opt into these semantics. |
| 60 | + |
| 61 | +## Proposed solution |
| 62 | + |
| 63 | +I propose to make existential types syntactically explicit in the language using the `any` keyword. This proposal introduces the new syntax in the Swift 5 language mode, and this syntax should be required for existential types under the Swift 6 language mode. |
| 64 | + |
| 65 | +In Swift 5, anywhere that an existential type can be used today, the `any` keyword can be used to explicitly denote an existential type: |
| 66 | + |
| 67 | +```swift |
| 68 | +// Swift 5 mode |
| 69 | + |
| 70 | +protocol P {} |
| 71 | +struct S: P {} |
| 72 | + |
| 73 | +let p1: P = S() // 'P' in this context is an existential type |
| 74 | +let p2: any P = S() // 'any P' is an explicit existential type |
| 75 | +``` |
| 76 | + |
| 77 | +In Swift 6, existential types are required be explicitly spelled with `any`: |
| 78 | + |
| 79 | +```swift |
| 80 | +// Swift 6 mode |
| 81 | + |
| 82 | +protocol P {} |
| 83 | +struct S: P {} |
| 84 | + |
| 85 | +let p1: P = S() // error |
| 86 | +let p2: any P = S() // okay |
| 87 | +``` |
| 88 | + |
| 89 | +## Detailed design |
| 90 | + |
| 91 | +### Grammar of explicit existential types |
| 92 | + |
| 93 | +This proposal adds the following production rules to the grammar of types: |
| 94 | + |
| 95 | +``` |
| 96 | +type -> existential-type |
| 97 | +
|
| 98 | +existential-type -> 'any' type |
| 99 | +``` |
| 100 | + |
| 101 | +### Semantics of explicit existential types |
| 102 | + |
| 103 | +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: |
| 104 | + |
| 105 | +```swift |
| 106 | +struct S {} |
| 107 | + |
| 108 | +let s: any S = S() // error: 'any' has no effect on concrete type 'S' |
| 109 | + |
| 110 | +func generic<T>(t: T) { |
| 111 | + let x: any T = t // error: 'any' has no effect on type parameter 'T' |
| 112 | +} |
| 113 | + |
| 114 | +let f: any ((Int) -> Void) = generic // error: 'any' has no effect on concrete type '(Int) -> Void' |
| 115 | +``` |
| 116 | + |
| 117 | +#### `Any` and `AnyObject` |
| 118 | + |
| 119 | +`any` is unnecessary for `Any` and `AnyObject` (unless part of a protocol composition): |
| 120 | + |
| 121 | +```swift |
| 122 | +struct S {} |
| 123 | +class C {} |
| 124 | + |
| 125 | +let value: any Any = S() // warning: 'any' is redundant on type 'Any' |
| 126 | +let values: [any Any] = [] // warning: 'any' is redundant on type 'Any' |
| 127 | +let object: any AnyObject = C() // warning: 'any' is redundant on type 'AnyObject' |
| 128 | + |
| 129 | +protocol P {} |
| 130 | +extension C: P {} |
| 131 | + |
| 132 | +let pObject: any AnyObject & P = C() // okay |
| 133 | +``` |
| 134 | + |
| 135 | +> **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. |
| 136 | +
|
| 137 | +#### Metatypes |
| 138 | + |
| 139 | +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`: |
| 140 | + |
| 141 | +```swift |
| 142 | +protocol P {} |
| 143 | +struct S: P {} |
| 144 | + |
| 145 | +let existentialMetatype: any P.Type = S.self |
| 146 | + |
| 147 | +protocol Q {} |
| 148 | +extension S: Q {} |
| 149 | + |
| 150 | +let compositionMetatype: any (P & Q).Type = S.self |
| 151 | + |
| 152 | +let protocolMetatype: (any P).Type = (any P).self |
| 153 | +``` |
| 154 | + |
| 155 | +> **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, which is naturally spelled `(any P).Type`. |
| 156 | +
|
| 157 | +Under this model, the `any` keyword conceptually acts like an existential quantifier `∃ T`. Formally, `any P.Type` means `∃ T:P . T.Type`, i.e. for some concrete type `T` conforming to `P`, this is the metatype of that concrete type.`(any P).Type` is formally `(∃ T:P . T).Type`, i.e. the metatype of the existential type itself. |
| 158 | + |
| 159 | +The distinction between `any P.Type` and `(any P).Type` is syntactically very subtle. However, `(any P).Type` is rarely useful in practice, and it's helpful to explain why, given a generic context where a type parameter `T` is substituted with an existential type, `T.Type` is the singleton protocol metatype. |
| 160 | + |
| 161 | +#### Type aliases and associated types |
| 162 | + |
| 163 | +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: |
| 164 | + |
| 165 | +```swift |
| 166 | +protocol P {} |
| 167 | +typealias AnotherP = P |
| 168 | +typealias AnyP = any P |
| 169 | + |
| 170 | +struct S: P {} |
| 171 | + |
| 172 | +let p2: any AnotherP = S() |
| 173 | +let p1: AnyP = S() |
| 174 | + |
| 175 | +func generic<T: AnotherP>(value: T) { ... } |
| 176 | +func generic<T: AnyP>(value: T) { ... } // error |
| 177 | +``` |
| 178 | + |
| 179 | +Once the `any` spelling is required under the Swift 6 language mode, a type alias to a plain protocol name is not a valid type witness for an associated type requirement; existential type witnesses must be explicit in the `typealias` with `any`: |
| 180 | + |
| 181 | +```swift |
| 182 | +// Swift 6 code |
| 183 | + |
| 184 | +protocol P {} |
| 185 | + |
| 186 | +protocol Requirements { |
| 187 | + associatedtype A |
| 188 | +} |
| 189 | + |
| 190 | +struct S1: Requirements { |
| 191 | + typealias A = P // error: associated type requirement cannot be satisfied with a protocol |
| 192 | +} |
| 193 | + |
| 194 | +struct S2: Requirements { |
| 195 | + typealias A = any P // okay |
| 196 | +} |
| 197 | +``` |
| 198 | + |
| 199 | +## Source compatibility |
| 200 | + |
| 201 | +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. |
| 202 | + |
| 203 | +[SE-0309 Unlock existentials for all protocols](https://github.com/apple/swift-evolution/blob/main/proposals/0309-unlock-existential-types-for-all-protocols.md) enables more code to be written using existential types. To minimize the amount of new code written that will become invalid in Swift 6, I propose requiring `any` immediately for protocols with `Self` and associated type requirements. This introduces an inconsistency for protocols under the Swift 5 language mode, but this inconsistency already exists today (because you cannot use certain protocols as existential types at all), and the syntax difference serves two purposes: |
| 204 | + |
| 205 | +1. It saves programmers time in the long run by preventing them from writing new code that will become invalid later. |
| 206 | +2. It communicates the existence of `any` and encourages programmers to start using it for other existential types before adopting Swift 6. |
| 207 | + |
| 208 | +## Effect on ABI stability |
| 209 | + |
| 210 | +None. |
| 211 | + |
| 212 | +## Effect on API resilience |
| 213 | + |
| 214 | +None. |
| 215 | + |
| 216 | +## Alternatives considered |
| 217 | + |
| 218 | +### Rename `Any` and `AnyObject` |
| 219 | + |
| 220 | +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. |
| 221 | + |
| 222 | +### Use `Any<P>` instead of `any P` |
| 223 | + |
| 224 | +A common suggestion is to spell existential types with angle brackets on `Any`, e.g. `Any<Hashable>`. `any P` has symmetry with `some P`, where both keywords can be applied to protocol constraints. The `Any<P>` syntax is also misleading because it appears that `Any` is a generic type, which is confusing to the mental model for 2 reasons: |
| 225 | + |
| 226 | +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. |
| 227 | +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. |
| 228 | + |
| 229 | +## Future Directions |
| 230 | + |
| 231 | +### Extending existential types |
| 232 | + |
| 233 | +This proposal provides an obvious syntax for extending existential types in order to manually implement protocol conformances: |
| 234 | + |
| 235 | +```swift |
| 236 | +extension any Equatable: Equatable { ... } |
| 237 | +``` |
| 238 | + |
| 239 | +### Re-purposing the plain protocol name |
| 240 | + |
| 241 | +In other places in the language, a plain protocol name is already sugar for a type parameter conforming to the protocol. Consider a normal protocol extension: |
| 242 | + |
| 243 | +```swift |
| 244 | +extension Collection { ... } |
| 245 | +``` |
| 246 | + |
| 247 | +This extension is a form of universal quantification; it extends all types that conform to `Collection`. This extension introduces a generic context with a type parameter `<Self: Collection>`, which means the above syntax is effectively sugar for a parameterized extension: |
| 248 | + |
| 249 | +```swift |
| 250 | +extension <Self> Self where Self: Collection { ... } |
| 251 | +``` |
| 252 | + |
| 253 | +Changing the syntax of existential types creates an opportunity to expand upon this sugar. If existential types are spelled explicitly with `any`, a plain protocol name could always mean sugar for a type parameter on the enclosing context with a conformance requirement to the protocol. For example, consider the declaration of `append(contentsOf:)` from the standard library: |
| 254 | + |
| 255 | +```swift |
| 256 | +extension Array { |
| 257 | + mutating func append<S: Sequence>(contentsOf newElements: S) where S.Element == Element |
| 258 | +} |
| 259 | +``` |
| 260 | + |
| 261 | +Combined with a syntax for constraining associated types in angle brackets, such as in [[Pitch] Light-weight same-type constraint syntax](https://forums.swift.org/t/pitch-light-weight-same-type-constraint-syntax/52889), the above declaration could be simplified to: |
| 262 | + |
| 263 | +```swift |
| 264 | +extension Array { |
| 265 | + mutating func append(contentsOf newElements: Sequence<Element>) |
| 266 | +} |
| 267 | +``` |
| 268 | + |
| 269 | +This sugar eliminates a lot of noise in cases where a type parameter is only referred to once in a generic signature, and it enforces a natural model of abstraction, where programmers only need to name an entity when they need to refer to it multiple times. |
| 270 | + |
| 271 | +## Revisions |
| 272 | + |
| 273 | +### Changes from the pitch discussion |
| 274 | + |
| 275 | +* Spell the existential metatype as `any P.Type`, and the protocol metatype as `(any P).Type`. |
| 276 | +* Preserve `any` through type aliases. |
| 277 | +* Allow `any` on `Any` and `AnyObject`. |
| 278 | + |
| 279 | +## Acknowledgments |
| 280 | + |
| 281 | +Thank you to Joe Groff, who originally suggested this direction and syntax in [Improving the UI of generics](https://forums.swift.org/t/improving-the-ui-of-generics/22814), and to those who advocated for this change in the recent discussion about [easing the learning curve for generics](https://forums.swift.org/t/discussion-easing-the-learning-curve-for-introducing-generic-parameters/52891). Thank you to John McCall and Slava Pestov, who helped me figure out the implementation model. |
0 commit comments