Skip to content

Commit 738fafb

Browse files
committed
Revisit lifetime dependency discussions
1 parent 127c206 commit 738fafb

File tree

1 file changed

+35
-85
lines changed

1 file changed

+35
-85
lines changed

proposals/NNNN-non-escapable.md

Lines changed: 35 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
We propose adding a new type constraint `~Escapable` for types that can be locally copied but cannot be assigned or transferred outside of the immediate context.
1515
This complements the `~Copyable` types added with SE-0390 by introducing another set of compile-time-enforced lifetime controls that can be used for safe, highly-performant APIs.
1616

17-
In addition, these types will support lifetime-dependency constraints (being tracked in a separate proposal), that allow them to safely hold pointers referring to data stored in other types.
17+
In addition, these types will support lifetime-dependency constraints (being tracked in a future proposal), that allow them to safely hold pointers referring to data stored in other types.
1818

1919
This feature is a key requirement for the proposed `Span` type.
2020

@@ -165,7 +165,7 @@ func f() {
165165

166166
#### Constraints on nonescapable parameters
167167

168-
A value of nonescapable type received as an parameter is subject to the same constraints as any other local variable.
168+
A value of nonescapable type received as a parameter is subject to the same constraints as any other local variable.
169169
In particular, a nonescapable `consuming` parameter (and all direct copies thereof) must actually be destroyed during the execution of the function.
170170
This is in contrast to an _escapable_ `consuming` parameter which can be disposed of by being returned or stored to an instance property or global variable.
171171

@@ -206,8 +206,8 @@ func f() -> NotEscapable { // 🛑 Cannot return a nonescapable type
206206
}
207207
```
208208

209-
A separate proposal describes “lifetime dependency annotations” that can relax this requirement by tying the lifetime of the returned value to the lifetime of another binding. The other binding can be a parameter of a function returning a vaule of a nonescapable type, or con be `self` for a method or computed property returning a value of a nonescapable type.
210-
In particular, struct and enum initializers (which build a new value and return it to the caller) cannot be written without some mechanism similar to that outlined in our companion proposal.
209+
A future proposal will describe “lifetime dependency annotations” that can relax this requirement by tying the lifetime of the returned value to the lifetime of another binding.
210+
In particular, struct and enum initializers (which build a new value and return it to the caller) cannot be written without some such mechanism.
211211

212212
#### Globals and static variables cannot be nonescapable
213213

@@ -219,7 +219,7 @@ This implies that they cannot be stored in global or static variables.
219219
Escaping closures cannot capture nonescapable values.
220220
Nonescaping closures can capture nonescapable values subject to the usual exclusivity restrictions.
221221

222-
Returning a nonescapable value from a closure requires explicit lifetime dependency annotations, as covered in the companion proposal.
222+
Returning a nonescapable value from a closure will only be possible with explicit lifetime dependency annotations, to be covered in a future proposal.
223223

224224
#### Nonescapable values and concurrency
225225

@@ -245,43 +245,14 @@ extension Box: Escapable where T: Escapable { }
245245
```
246246

247247
This can be used in conjunction with other suppressible protocols.
248-
For example, many general library container types will need to be copyable and/or escapable according to their contents.
248+
For example, many general library container types will need to be copyable and/or escapable depending on their contents.
249249
Here's a compact way to declare such a type:
250250
```swift
251-
struct Wrapper<T: ~Copyable & ~Escapable> { ... }
252-
extension Wrapper: Copyable where T: ~Escapable {}
253-
extension Wrapper: Escapable where T: ~Copyable {}
251+
struct Wrapper<T: ~Copyable & ~Escapable>: ~Copyable, ~Escapable { ... }
252+
extension Wrapper: Copyable where T: Copyable {}
253+
extension Wrapper: Escapable where T: Escapable {}
254254
```
255255

256-
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`.
257-
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
258-
```swift
259-
struct Wrapper<T: ~Copyable & ~Escapable>: ~Copyable & ~Escapable {}
260-
```
261-
262-
Now recall from SE-427 that suppressible protocols must be explicitly suppressed on type parameters in extensions.
263-
This means that
264-
```swift
265-
extension Wrapper: Copyable where T: ~Escapable {}
266-
```
267-
is exactly the same as
268-
```swift
269-
extension Wrapper: Copyable where T: Copyable & ~Escapable {}
270-
```
271-
which implies that `Wrapper` becomes `Copyable` when `T` is `Copyable`.
272-
Finally, remember that `~Escapable` means that `Escapable` is not required, so
273-
this condition on `Copyable` applies regardless of whether `T` is `Escapable` or not.
274-
275-
Similarly,
276-
```swift
277-
extension Wrapper: Escapable where T: ~Copyable {}
278-
```
279-
is exactly the same as
280-
```swift
281-
extension Wrapper: Escapable where T: Escapable & ~Copyable {}
282-
```
283-
which means that `Wrapper` is `Escapable` whenever `T` is `Escapable` regardless of whether `T` is `Copyable` or not.
284-
285256
## Source compatibility
286257

287258
The compiler will treat any type without explicit `~Escapable` as escapable.
@@ -316,24 +287,36 @@ Briefly, this type would provide an efficient universal “view” of array-like
316287
Since values of this type do not own any data but only refer to data stored elsewhere, their lifetime must be limited to not exceed that of the owning storage.
317288
We expect to publish a sample implementation and proposal for that type very soon.
318289

319-
#### Lifetime dependency annotations
290+
#### Initializers and Lifetime Dependencies
320291

321-
Nonescapable types have a set of inherent restrictions on how they can be passed as arguments, stored in variables, or returned from functions.
322-
A companion proposal builds on this by supporting more detailed annotations that link the lifetimes of different objects.
323-
This would allow, for example, a container to vend an iterator value that held a direct unmanaged pointer to the container's contents.
324-
The lifetime dependency would ensure that such an iterator could not outlive the container to whose contents it referred.
292+
All values come into existence within the body of some initializer and are returned to the caller of that initializer.
293+
Since nonescapable types cannot be returned,
294+
it follows that nonescapable types cannot have initializers without some additional language affordance.
325295

296+
A subsequent proposal will provide such an affordance.
297+
This will allow values to be returned subject to the requirement that they not outlive some other specific value.
298+
For example, a nonescapable iterator might be initialized so as to not outlive the container that created it:
326299
```swift
327-
// Example: Nonescaping iterator
328-
struct NEIterator {
329-
// `dependsOn(container)` indicates that the constructed value
330-
// cannot outlive the `container` argument.
331-
init(over container: MyContainer) -> dependsOn(container) Self {
332-
... initialize an iterator suitable for `MyContainer` ...
333-
}
300+
struct Iterator: ~Escapable {
301+
// ⚠️️ Returned Iterator will not be allowed to outlive `container`
302+
// Details in a future proposal: This may involve
303+
// additional syntax or default inference rules.
304+
init(container: borrowing Container) { ... }
334305
}
306+
307+
let iterator: Iterator
308+
do {
309+
let container = Container(...)
310+
let buffer = container.buffer
311+
iterator = Iterator(buffer)
312+
// `container` lifetime ends here
313+
}
314+
use(iterator) // 🛑 'iterator' outlives `container`
335315
```
336316

317+
These lifetime dependencies will be enforced entirely at compile time without any runtime overhead.
318+
Invalid uses such as the one above will produce compiler errors.
319+
337320
#### Expanding standard library types
338321

339322
We expect that many standard library types will need to be updated to support possibly-nonescapable types, including `Optional`, `Array`, `Set`, `Dictionary`, and the `Unsafe*Pointer` family of types.
@@ -349,7 +332,8 @@ For example, this can greatly improve the safety of locking APIs that expect to
349332

350333
#### Nonescapable classes
351334

352-
We’ve explicitly excluded class types from being nonescapable. In the future, we could allow class types to be declared nonescapable as a way to avoid most reference-counting operations on class objects.
335+
We’ve explicitly excluded class types from being nonescapable.
336+
In the future, we could allow class types to be declared nonescapable as a way to avoid most reference-counting operations on class objects.
353337

354338
#### Concurrency
355339

@@ -407,40 +391,6 @@ The iterator example in the beginning of this document provides another motivati
407391
Iterators are routinely copied in order to record a particular point in a collection.
408392
Thus we concluded that non-copyable was not the correct lifetime restriction for types of this sort, and it was worthwhile to introduce a new lifetime concept to the language.
409393

410-
#### Returns and initializers
411-
412-
This proposal does not by itself provide any way to initialize a nonescapable value, requiring the additional proposed lifetime dependency annotations to support that mechanism.
413-
Since those annotations require that the lifetime of the returned value be bound to that of one of the arguments, this implies that our current proposal does not permit nonescapable types to have trivial initializers:
414-
415-
```swift
416-
struct NE: ~Escapable {
417-
init() {} // 🛑 Initializer return must depend on an argument
418-
}
419-
```
420-
421-
We considered introducing an annotation that would specifically allow this and related uses:
422-
423-
```swift
424-
struct NE: ~Escapable {
425-
@_unsafeNonescapableResult
426-
init() {} // OK because of annotation
427-
}
428-
```
429-
430-
We omitted this annotation from our proposal because there is more than one possible interpretation of such a marker. And we did not see a compelling reason for preferring one particular interpretation because we have yet to find a use case that actually requires this.
431-
432-
In particular, the use cases we’ve so far considered have all been resolvable by adding an argument specifically for the purpose of anchoring a lifetime dependency:
433-
434-
```swift
435-
struct NE: ~Escapable {
436-
// Proposed lifetime dependency notation;
437-
// see separate proposal for details.
438-
init(from: SomeType) -> dependsOn(from) Self {}
439-
}
440-
```
441-
442-
We expect that future experience with nonescapable types will clarify whether additional lifetime modifiers of this sort are justified.
443-
444394
## Acknowledgements
445395

446396
Many people discussed this proposal and gave important feedback, including: Kavon Farvardin, Meghana Gupta, John McCall, Slava Pestov, Joe Groff, Guillaume Lessard, and Franz Busch.

0 commit comments

Comments
 (0)