Skip to content

Commit 9ab1df6

Browse files
committed
More revisions from feedback
- `unsafeLifetime` is currently implemented as `_overrideLifetime` - Clarify distinction between using an inout parameter as a source or target, and point out what occurs (nothing) if a parameter is both the source and target of a dependency - Give an explicit example of conjoined dependence with multiple lifetimes on the same target
1 parent e43a19e commit 9ab1df6

File tree

1 file changed

+59
-18
lines changed

1 file changed

+59
-18
lines changed

proposals/NNNN-lifetime-dependency.md

+59-18
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ This is deeply related to `~Escapable` types, as introduced in [SE-0446](0446-no
1515
**Edited** (March 20, 2025):
1616

1717
- Replaced `dependsOn` return type modifier with a declaration-level `@lifetime` attribute.
18-
Removed dependency inference rules.
18+
Removed dependency inference rules.
1919
- Integrated links to proposals SE-0446 (`Escapable`), SE-0447 (`Span`), SE-0456 (`Span`-producing properties), and SE-0467 (`MutableSpan`) that have undergone review.
20-
- Added SE-0458 `@unsafe` annotations to the `unsafeLifetime` standard library functions, and added `@unsafe` as a requirement for APIs using `BitwiseCopyable` lifetime dependencies under strict memory safety.
20+
- Added SE-0458 `@unsafe` annotations to the `_overrideLifetime` standard library functions, and added `@unsafe` as a requirement for APIs using `BitwiseCopyable` lifetime dependencies under strict memory safety.
2121

2222
**Edited** (April 12, 2024): Changed `@dependsOn` to `dependsOn` to match the current implementation.
2323

@@ -547,27 +547,37 @@ unsafe storage.withUnsafeBufferPointer { buffer in
547547

548548
### Standard library extensions
549549

550-
#### `unsafeLifetime` helper functions
550+
#### `_overrideLifetime` helper functions
551551

552-
The following two helper functions will be added for implementing low-level data types:
552+
The following helper functions will be added for implementing low-level data types:
553553

554554
```swift
555555
/// Replace the current lifetime dependency of `dependent` with a new copied lifetime dependency on `source`.
556556
///
557557
/// Precondition: `dependent` has an independent copy of the dependent state captured by `source`.
558558
@unsafe @lifetime(copy source)
559-
func unsafeLifetime<T: ~Copyable & ~Escapable, U: ~Copyable & ~Escapable>(
560-
dependent: consuming T, dependsOn source: borrowing U)
559+
func _overrideLifetime<T: ~Copyable & ~Escapable, U: ~Copyable & ~Escapable>(
560+
dependent: consuming T, copying source: borrowing U)
561561
-> T { ... }
562562

563563
/// Replace the current lifetime dependency of `dependent` with a new scoped lifetime dependency on `source`.
564564
///
565565
/// Precondition: `dependent` depends on state that remains valid until either:
566566
/// (a) `source` is either destroyed if it is immutable,
567567
/// or (b) exclusive to `source` access ends if it is a mutable variable.
568-
@unsafe @lifetime(borrowing source)
569-
func unsafeLifetime<T: ~Copyable & ~Escapable, U: ~Copyable & ~Escapable>(
570-
dependent: consuming T, scoped source: borrowing U)
568+
@unsafe @lifetime(borrow source)
569+
func _overrideLifetime<T: ~Copyable & ~Escapable, U: ~Copyable & ~Escapable>(
570+
dependent: consuming T, borrowing source: borrowing U)
571+
-> T {...}
572+
573+
/// Replace the current lifetime dependency of `dependent` with a new scoped lifetime dependency on `source`.
574+
///
575+
/// Precondition: `dependent` depends on state that remains valid until either:
576+
/// (a) `source` is either destroyed if it is immutable,
577+
/// or (b) exclusive to `source` access ends if it is a mutable variable.
578+
@unsafe @lifetime(inout source)
579+
func _overrideLifetime<T: ~Copyable & ~Escapable, U: ~Copyable & ~Escapable>(
580+
dependent: consuming T, mutating source: inout U)
571581
-> T {...}
572582
```
573583

@@ -578,20 +588,20 @@ extension Span {
578588
consuming func dropFirst() -> Span<Element> {
579589
let local = Span(base: self.base + 1, count: self.count - 1)
580590
// 'local' can persist after 'self' is destroyed.
581-
return unsafe unsafeLifetime(dependent: local, dependsOn: self)
591+
return unsafe _overrideLifetime(dependent: local, dependsOn: self)
582592
}
583593
}
584594
```
585595

586-
Since `self.base` is an `Escapable` value, it does not propagate the lifetime dependence of its container. Without the call to `unsafeLifetime`, `local` would be limited to the local scope of the value retrieved from `self.base`, and could not be returned from the method. In this example, `unsafeLifetime` communicates that all of the dependent state from `self` has been *copied* into `local`, and, therefore, `local` can persist after `self` is destroyed.
596+
Since `self.base` is an `Escapable` value, it does not propagate the lifetime dependence of its container. Without the call to `_overrideLifetime`, `local` would be limited to the local scope of the value retrieved from `self.base`, and could not be returned from the method. In this example, `_overrideLifetime` communicates that all of the dependent state from `self` has been *copied* into `local`, and, therefore, `local` can persist after `self` is destroyed.
587597

588-
`unsafeLifetime` can also be used to construct an immortal value where the compiler cannot prove immortality by passing a `Void` value as the source of the dependence:
598+
`_overrideLifetime` can also be used to construct an immortal value where the compiler cannot prove immortality by passing a `Void` value as the source of the dependence:
589599

590600
```swift
591601
@lifetime(immortal)
592602
init() {
593603
self.value = getGlobalConstant() // OK: unchecked dependence.
594-
self = unsafe unsafeLifetime(dependent: self, dependsOn: ())
604+
self = unsafe _overrideLifetime(dependent: self, dependsOn: ())
595605
}
596606
```
597607

@@ -651,11 +661,11 @@ This relationship obeys the following requirements:
651661

652662
* The dependent value must be potentially non-`Escapable`.
653663

654-
* The dependent value's lifetime must not be longer than that of the source value.
664+
* The dependent value's lifetime must be as long as or shorter than that of the source value.
655665

656666
* The dependent value is treated as an ongoing access to the source value.
657-
Following Swift's usual exclusivity rules, the source value may not be mutated during the lifetime of the dependent value;
658-
if the access is a mutating access, the source value is further prohibited from being accessed at all during the lifetime of the dependent value.
667+
Following Swift's usual exclusivity rules, the source value may not be mutated during the lifetime of the dependent value;
668+
if the access is a mutating access, the source value is further prohibited from being accessed at all during the lifetime of the dependent value.
659669

660670
The compiler must issue a diagnostic if any of the above cannot be satisfied.
661671

@@ -806,6 +816,19 @@ func reassignWithArgDependence(_ span: inout Span<Int>, _ arg: ContiguousArray<I
806816
}
807817
```
808818

819+
This means that an `inout` parameter of potentially non-`Escapable` type can interact with lifetimes in three ways:
820+
821+
- as the source of a scoped dependency, as in `@lifetime([<target>:] inout x)`
822+
- as the source of a copied dependency, as in `@lifetime([<target>:] copy x)`
823+
- as the target of a dependency, as in `@lifetime(x: <dependency>)`
824+
825+
so it is worth restating the behavior here to emphasize the distinctions.
826+
A scoped dependency `@lifetime(inout x)` indicates that the target's lifetime is constrained by exclusive access to `x`.
827+
A copied dependency `@lifetime(copy x)` indicates that the target copies its lifetime constraint from value of `x` when the callee *begins* execution.
828+
As the target of a dependency, `@lifetime(x: <dependency>)` indicates the lifetime constraint added to the value of `x` after the callee *ends* execution.
829+
830+
By composition, an `inout` parameter can appear as both the source and target of a dependency, although there is no net effect to doing so. `@lifetime(x: inout x)` states that the value of `x` on return from the callee is dependent on exclusive access to the variable `x`, but any value that can be stored in a mutable variable must already live at least as long as any exclusive access to `x` that could occur. `@lifetime(x: copy x)` states that the value of `x` on return from the callee copies its dependency from the value of `x` when the function began execution, in effect stating that the lifetime dependency does not change.
831+
809832
#### Conditional reassignment creates conjoined dependence
810833

811834
`inout` argument dependence behaves like a conditional reassignment. After the call, the variable passed to the `inout` argument has both its original dependence along with a new dependence on the argument that is the source of the argument dependence.
@@ -821,6 +844,24 @@ do {
821844
parse(span) // 🛑 Error: 'span' escapes the scope of 'a2'
822845
```
823846

847+
#### Explicit conjoined dependence
848+
849+
A declaration can also express a conjoined dependence explicitly by applying multiple lifetime dependencies to the same target:
850+
851+
```swift
852+
struct Pair<T: ~Escapable, U: ~Escapable>: ~Escapable {
853+
var first: T
854+
var second: U
855+
856+
// A Pair cannot outlive the lifetime of either of its fields.
857+
@lifetime(copy first, copy second)
858+
init(first: T, second: U) {
859+
self.first = first
860+
self.second = second
861+
}
862+
}
863+
```
864+
824865
#### `Escapable` properties in a non-`Escapable` type
825866

826867
A non-`Escapable` type inevitably contains `Escapable` properties.
@@ -899,14 +940,14 @@ This poses a few problems:
899940

900941
2. `lifetime(unchecked)` is a blunt tool for opting out of safety. Experience shows that such tools are overused as workarounds for compiler errors without fixing the problem. A safety workaround should more precisely identify the source of unsafety.
901942

902-
`unsafeLifetime` is the proposed tool for disabling dependence checks. Passing `Void` as the dependence source is a reasonable way to convert a nonescaping value to an immortal value:
943+
`_overrideLifetime` is the proposed tool for disabling dependence checks. Passing `Void` as the dependence source is a reasonable way to convert a nonescaping value to an immortal value:
903944

904945

905946
```swift
906947
@lifetime(immortal)
907948
init() dependsOn(immortal) {
908949
self.value = getGlobalConstant() // OK: unchecked dependence.
909-
unsafe self = unsafeLifetime(dependent: self, dependsOn: ())
950+
unsafe self = _overrideLifetime(dependent: self, dependsOn: ())
910951
}
911952
```
912953

0 commit comments

Comments
 (0)