You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: proposals/NNNN-lifetime-dependency.md
+130Lines changed: 130 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -589,6 +589,136 @@ In this case, the compiler will infer a dependency on the unique argument identi
589
589
**In no other case** will a function, method, or initializer implicitly gain a lifetime dependency.
590
590
If a function, method, or initializer has a nonescapable return value, does not have an explicit lifetime dependency annotation, and does not fall into one of the cases above, then that will be a compile-time error.
591
591
592
+
593
+
### Dependency semantics by example
594
+
595
+
This section illustrates the semantics of lifetime dependence one example at a time for each interesting variation. The following helper functions will be useful: `Array.span()` creates a scoped dependence to a nonescapable `Span` result, `copySpan()` creates a copied dependence to a `Span` result, and `parse` uses a `Span`.
596
+
597
+
```swift
598
+
extensionArray {
599
+
// The returned span depends on the scope of Self.
parse(span) // 🛑 Error: 'span' escapes the scope of 'a2'
619
+
```
620
+
621
+
The call to `span()` creates a scoped dependence on `a2`. A scoped dependence is determined by the lifetime of the variable, not the lifetime of the value assigned to that variable. So the lifetime of `span` cannot extend into the larger lifetime of `a`.
622
+
623
+
#### Copied dependence on an immutable variable
624
+
625
+
Let's contrast scoped dependence shown above with copied dependence on a variable. In this case, the value may outlive the variable it is copied from, as long as it is destroyed before the root of its inherited dependence goes out of scope. A chain of copied dependencies is always rooted in a scoped dependence.
626
+
627
+
An assignment that copies or moves a nonescapable value from one variable into another **copies** any lifetime dependence from the source value to the destination value. Thus, variable assignment has the same lifetime copy semantics as passing an argument using a `dependsOn()` annotation *without* a `scoped` keyword. So, the statement `let temp = span` has identical semantics to `let temp = copySpan(span)`.
628
+
629
+
```swift
630
+
let a: Array<Int> = arg
631
+
let final: Span<Int>
632
+
do {
633
+
let span = a.span()
634
+
let temp = span
635
+
final=copySpan(temp)
636
+
}
637
+
parse(final) // ✅ Safe: still within lifetime of 'a'
638
+
```
639
+
640
+
Although the result of `copySpan` depends on `temp`, the result of the copy may be used outside of the `temp`'s lexical scope. Following the source of each copied dependence, up through the call chain if needed, eventually leads to the scoped dependence root. Here, `final` is the end of a lifetime dependence chain rooted at a scoped dependence on `a`:
641
+
`a -> span -> temp -> {copySpan argument} -> final`. `final` is therefore valid within the scope of `a` even if the intermediate copies have been destroyed.
A dependence may be copied from a mutable ('inout') variable. In that case, the dependence is inherited from whatever value the mutable variable holds when it is accessed.
659
+
660
+
```swift
661
+
let a: Array<Int> =...
662
+
var prefix: Span<Int>
663
+
do {
664
+
var temp = a.span()
665
+
prefix= temp.droppingPrefix(length: 1) // access 'temp' as 'inout'
666
+
// 'prefix' depends on 'a', not 'temp'
667
+
}
668
+
parse(prefix) // ✅ Safe: still within lifetime of 'a'
669
+
```
670
+
671
+
#### Scoped dependence on 'inout' access
672
+
673
+
Now, let's return to scoped dependence, this time on a mutable variable. This is where exclusivity guarantees come into play. A scoped depenendence extends an access of the mutable variable across all uses of the dependent value. If the variable mutates again before the last use of the dependent, then it is an exclusivity violation.
674
+
675
+
```swift
676
+
let a: Array<Int> =...
677
+
a[i] =...
678
+
let span = a1.span()
679
+
parse(span) // ✅ Safe: still within 'span's access on 'a'
680
+
a[i] =...
681
+
parse(span) // 🛑 Error: simultaneous access of 'a'
682
+
```
683
+
684
+
Here, `a1.span()` initiates a 'read' access on `a1`. The first call to `parse(span)` safely extends that read access. The read cannot extend to the second call because a mutation of `a1` occurs before it.
685
+
686
+
#### Dependence reassignment
687
+
688
+
We've described how a mutable variable can be the source of a lifetime dependence. Now let's look at nonescapable mutable variables. Being nonescapable means they depend on another lifetime. Being mutable means that dependence may change during reassignment. Reassigning a nonescapable 'inout' sets its lifetime dependence from that point on, up to either the end of the variable's lifetime or its next subsequent reassignment.
689
+
690
+
```swift
691
+
funcreassign(_span: inout Span<Int>) {
692
+
let a: Array<Int> =...
693
+
span = a.span() // 🛑 Error: 'span' escapes the scope of 'a'
694
+
}
695
+
```
696
+
697
+
#### Reassignment with argument dependence
698
+
699
+
If a function takes a nonescapable 'inout' argument, it may only reassign that argument if it is marked dependent on another function argument that provies the source of the dependence.
'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.
710
+
711
+
```swift
712
+
let a1: Array<Int> = arg
713
+
do {
714
+
let a2: Array<Int> = arg
715
+
var span = a1.span()
716
+
testReassignArgDependence(&span, a2) // creates a conjoined dependence
717
+
parse(span) // ✅ OK: within the lifetime of 'a1' & 'a2'
718
+
}
719
+
parse(span) // 🛑 Error: 'span' escapes the scope of 'a2'
720
+
```
721
+
592
722
## Source compatibility
593
723
594
724
Everything discussed here is additive to the existing Swift grammar and type system.
0 commit comments