Skip to content

Commit 54999f0

Browse files
committed
New subsection on conditional escapability; many small edits
1 parent 12b07c2 commit 54999f0

File tree

1 file changed

+67
-29
lines changed

1 file changed

+67
-29
lines changed

proposals/NNNN-non-escapable.md

Lines changed: 67 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ In addition, the use of reference counting to ensure correctness at runtime make
4141
## Proposed solution
4242

4343
Currently, the notion of "escapability" appears in the Swift language as a feature of closures.
44-
Closures that are declared as `@nonescapable` can use a very efficient stack-based representation;
45-
closures that are `@escapable` store their state on the heap.
44+
Nonescapable closures can use a very efficient stack-based representation;
45+
closures that are `@escapable` store their captures on the heap.
4646

4747
By allowing Swift developers to mark various types as nonescapable, we provide a mechanism for them to opt into a specific set of usage limitations that:
4848

@@ -64,7 +64,7 @@ We are not at this time proposing any changes to Swift's current `Iterator` prot
6464

6565
#### New Escapable Concept
6666

67-
We add a new suppressible type constraint `Escapable` to the standard library and implicitly apply it to all current Swift types (with the sole exception of `@nonescapable` closures).
67+
We add a new suppressible protocol `Escapable` to the standard library and implicitly apply it to all current Swift types (with the sole exception of nonescapable closures).
6868
`Escapable` types can be assigned to global variables, passed into arbitrary functions, or returned from the current function or closure.
6969
This matches the existing semantics of all Swift types prior to this proposal.
7070

@@ -77,7 +77,7 @@ protocol Escapable: ~Copyable {}
7777

7878
#### In concrete contexts, `~Escapable` indicates nonescapability
7979

80-
Using the same approach as used for `~Copyable` and `Copyable`, we use `~Escapable` to indicate the lack of the `Escapable` attribute on a type.
80+
Using the same approach as used for `~Copyable` and `Copyable`, we use `~Escapable` to suppress the `Escapable` conformance on a type.
8181

8282
```swift
8383
// Example: A type that is not escapable
@@ -86,7 +86,7 @@ struct NotEscapable: ~Escapable {
8686
}
8787
```
8888

89-
A nonescapable type is not allowed to escape the local context:
89+
A nonescapable value is not allowed to escape the local context:
9090
```swift
9191
// Example: Basic limits on ~Escapable types
9292
func f() -> NotEscapable {
@@ -98,10 +98,10 @@ func f() -> NotEscapable {
9898
}
9999
```
100100

101-
**Note**: The inability to return a nonescapable type has implications for how initializers must be written.
102-
The section "Returned nonescapable values require lifetime dependency" has more details.
101+
**Note**:
102+
The section "Returned nonescapable values require lifetime dependency" explains the implications for how you must write initializers.
103103

104-
Without a `~Escapable` marker, the default for any type is to be escapable. Since `~Escapable` indicates the lack of a capability, you cannot put this in an extension.
104+
Without `~Escapable`, the default for any type is to be escapable. Since `~Escapable` suppresses a capability, you cannot put this in an extension.
105105

106106
```swift
107107
// Example: Escapable by default
@@ -111,15 +111,13 @@ extension Ordinary: ~Escapable // 🛑 Extensions cannot remove a capability
111111

112112
Classes cannot be declared `~Escapable`.
113113

114-
#### In generic contexts, `~Escapable` marks the lack of an Escapable requirement
114+
#### In generic contexts, `~Escapable` suppresses the default Escapable requirement
115115

116116
When used in a generic context, `~Escapable` allows you to define functions or types that can work with values that might or might not be escapable.
117-
That is, `~Escapable` indicates the lack of an escapable requirement.
117+
That is, `~Escapable` indicates the default escapable requirement has been suppressed.
118118
Since the values might not be escapable, the compiler must conservatively prevent the values from escaping:
119119

120120
```swift
121-
// Example: In generic contexts, ~Escapable is
122-
// the lack of an Escapable requirement.
123121
func f<MaybeEscapable: ~Escapable>(_ value: MaybeEscapable) {
124122
// `value` might or might not be Escapable
125123
globalVar = value // 🛑 Cannot assign possibly-nonescapable type to a global var
@@ -128,21 +126,6 @@ f(NotEscapable()) // Ok to call with nonescapable argument
128126
f(7) // Ok to call with escapable argument
129127
```
130128

131-
This also permits the definition of types whose escapability varies depending on their generic arguments.
132-
As with other conditional behaviors, this is expressed by using an extension to conditionally add a new capability to the type:
133-
134-
```swift
135-
// Example: Conditionally Escapable generic type
136-
// By default, Box is itself nonescapable
137-
struct Box<T: ~Escapable>: ~Escapable {
138-
var t: T
139-
}
140-
141-
// Box gains the ability to escape whenever its
142-
// generic argument is Escapable
143-
extension Box: Escapable when T: Escapable { }
144-
```
145-
146129
[SE-0427 Noncopyable Generics](https://github.com/apple/swift-evolution/blob/main/proposals/0427-noncopyable-generics.md) provides more detail on
147130
how suppressible protocols such as `Escapable` are handled in the generic type system.
148131

@@ -238,9 +221,64 @@ All of the requirements on use of nonescapable values as function arguments and
238221

239222
The closures used in `Task.init`, `Task.detached`, or `TaskGroup.addTask` are escaping closures and therefore cannot capture nonescapable values.
240223

224+
#### Conditionally `Escapable` types
225+
226+
You can define types whose escapability varies depending on their generic arguments.
227+
As with other conditional behaviors, this is expressed by using an extension to conditionally add a new capability to the type:
228+
229+
```swift
230+
// Example: Conditionally Escapable generic type
231+
// By default, Box is itself nonescapable
232+
struct Box<T: ~Escapable>: ~Escapable {
233+
var t: T
234+
}
235+
236+
// Box gains the ability to escape whenever its
237+
// generic argument is Escapable
238+
extension Box: Escapable when T: Escapable { }
239+
```
240+
241+
This can be used in conjunction with other suppressible protocols.
242+
For example, many general library types will need to be copyable and/or escapable following their contents.
243+
Here's a compact way to declare such a type:
244+
```swift
245+
struct Wrapper<T: ~Copyable & ~Escapable> { ... }
246+
extension Wrapper: Copyable where T: ~Escapable {}
247+
extension Wrapper: Escapable where T: ~Copyable {}
248+
```
249+
250+
The above declarations all in a single source file will result in a type `Wrapper` that is `Escapable` exactly when `T` is `Escapable` and `Copyable` exactly when `T` is `Copyable`.
251+
To see why, first note that the explicit `extension Wrapper: Escapable` in the same source file implies that the original `struct Wrapper` must be `~Escapable` and similarly for `Copyable`, exactly as if the first line had been
252+
```swift
253+
struct Wrapper<T: ~Copyable & ~Escapable>: ~Copyable & ~Escapable {}
254+
```
255+
256+
Now recall from SE-427 that suppressible protocols must be explicitly suppressed on type parameters in extensions.
257+
This means that
258+
```swift
259+
extension Wrapper: Copyable where T: ~Escapable {}
260+
```
261+
is exactly the same as
262+
```swift
263+
extension Wrapper: Copyable where T: Copyable & ~Escapable {}
264+
```
265+
which implies that `Wrapper` becomes `Copyable` when `T` is `Copyable`.
266+
Finally, remember that `~Escapable` means that `Escapable` is not required, so
267+
this condition on `Copyable` applies regardless of whether `T` is `Escapable` or not.
268+
269+
Similarly,
270+
```swift
271+
extension Wrapper: Escapable where T: ~Copyable {}
272+
```
273+
is exactly the same as
274+
```swift
275+
extension Wrapper: Escapable where T: Escapable & ~Copyable {}
276+
```
277+
which means that `Wrapper` is `Escapable` whenever `T` is `Escapable` regardless of whether `T` is `Copyable` or not.
278+
241279
## Source compatibility
242280

243-
The compiler will treat any type without an explicit `~Escapable` marker as escapable.
281+
The compiler will treat any type without explicit `~Escapable` as escapable.
244282
This matches the current behavior of the language.
245283

246284
Only when new types are marked as `~Escapable` does this have any impact.
@@ -325,7 +363,7 @@ We expect to eventually allow this by explicitly annotating a “static” or
325363
#### Require `Escapable` to indicate escapable types without using `~Escapable`
326364

327365
We could avoid using `~Escapable` to mark types that lack the `Escapable` property by requiring `Escapable` on all escapable types.
328-
However, it is infeasible to require updating all existing types in all existing Swift code with a new capability marker.
366+
However, it is infeasible to require updating all existing types in all existing Swift code with a new explicit capability.
329367

330368
Apart from that, we expect almost all types to continue to be escapable in the future, so the negative marker reduces the overall burden.
331369
It is also consistent with progressive disclosure:

0 commit comments

Comments
 (0)