Skip to content

Commit 70b9775

Browse files
committed
Update 'any' proposal based on feedback from the pitch discussion.
1 parent 077b100 commit 70b9775

File tree

1 file changed

+78
-16
lines changed

1 file changed

+78
-16
lines changed

proposals/NNNN-existential-any.md

+78-16
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
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`.
1212

13+
Swift evolution discussion thread: [[Pitch] Introduce existential `any`](https://forums.swift.org/t/pitch-introduce-existential-any/53520).
14+
1315
## Motivation
1416

1517
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:
@@ -35,7 +37,7 @@ Despite these significant and often undesirable implications, existential types
3537

3638
## Proposed solution
3739

38-
I propose to make existential types syntactically explicit in the language using the `any` keyword. This proposal introduces the new syntax, and this syntax should be required under the Swift 6 language mode.
40+
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.
3941

4042
## Detailed design
4143

@@ -51,7 +53,7 @@ existential-type -> 'any' type
5153

5254
### Semantics of explicit existential types
5355

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:
5557

5658
```swift
5759
struct S {}
@@ -61,33 +63,84 @@ let s: any S = S() // error: 'any' has no effect on concrete type 'S'
6163
func generic<T>(t: T) {
6264
let x: any T = t // error: 'any' has no effect on type parameter 'T'
6365
}
66+
67+
let f: any ((Int) -> Void) = generic // error: 'any' has no effect on concrete type '(Int) -> Void'
6468
```
6569

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):
71+
72+
```swift
73+
struct S {}
74+
class C {}
75+
76+
let value: any Any = S() // warning: 'any' is redundant on type 'Any'
77+
let values: [any Any] = [] // warning: 'any' is redundant on type 'Any'
78+
let object: any AnyObject = C() // warning: 'any' is redundant on type 'AnyObject'
6779

80+
protocol P {}
81+
extension C: P {}
6882

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.
83+
let pObject: any AnyObject & P = C() // okay
84+
```
7085

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.
7187
72-
The existential metatype becomes `(any P).Type`, and the protocol metatype remains `P.Protocol`.
88+
#### Metatypes
7389

74-
Examples
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`:
7591

7692
```swift
7793
protocol P {}
78-
class C {}
94+
struct S: P {}
95+
96+
let existentialMetatype: any P.Type = S.self
97+
98+
protocol Q {}
99+
extension S: Q {}
100+
101+
let compositionMetatype: any (P & Q).Type = S.self
102+
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+
protocol P {}
114+
typealias AnotherP = P
115+
typealias AnyP = any P
79116

80-
any P
81-
any Any // error
82-
any AnyObject // error
83-
any P & AnyObject
84-
any C // error
85-
any P & C
86-
any () -> Void // error
117+
struct S: P {}
87118

88-
(any P).Type
119+
let p2: any AnotherP = S()
120+
let p1: AnyP = S()
89121

90-
func test<T>(_: T) where T == any P
122+
func generic<T: AnotherP>(value: T) { ... }
123+
func generic<T: AnyP>(value: T) { ... } // error
124+
```
125+
126+
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`:
127+
128+
```swift
129+
// Swift 6 code
130+
131+
protocol P {}
132+
133+
protocol Requirements {
134+
associatedtype A
135+
}
136+
137+
struct S1: Requirements {
138+
typealias A = P // error: associated type requirement cannot be satisfied with a protocol
139+
}
140+
141+
struct S2: Requirements {
142+
typealias A = any P // okay
143+
}
91144
```
92145

93146
## Source compatibility
@@ -109,8 +162,17 @@ None.
109162

110163
## Alternatives considered
111164

165+
### Rename `Any` and `AnyObject`
166+
112167
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.
113168

169+
### Use `Any<P>` instead of `any P`
170+
171+
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:
172+
173+
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.
174+
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.
175+
114176
## Future Directions
115177

116178
### Extending existential types

0 commit comments

Comments
 (0)