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
In the current design, aggregating multiple values merges their scopes.
802
+
803
+
```swift
804
+
structContainer<Element>: ~Escapable {
805
+
var a: /*dependsOn(self)*/Element
806
+
var b: /*dependsOn(self)*/Element
807
+
808
+
init(a: Element, b: Element) -> dependsOn(a, b) Self {...}
809
+
}
810
+
```
811
+
812
+
This can have the effect of narrowing the lifetime scope of some components:
813
+
814
+
```swift
815
+
var a =...
816
+
{
817
+
let b =...
818
+
let c = Container<Element>(a: a, b: b)
819
+
a = c.a// 🛑 Error: `a` outlives `c.a`, which is constrained by the lifetime of `b`
820
+
}
821
+
```
822
+
823
+
In the future, the lifetimes of multiple values can be represented independently by attaching a `@lifetime` attribute to a stored property and referring to that property's name inside `dependsOn` annotations:
The nesting level of a component is the inverse of the nesting level of its lifetime. `a` and `b` are nested components of `Container`, but the lifetime of a `Container` instance is nested within both lifetimes of `a` and `b`.
837
+
838
+
### Abstract lifetime components
839
+
840
+
Lifetime dependence is not always neatly tied to stored properties. Say that our `Container` now holds multiple elements within its own storage. We can use a top-level `@lifetime` annotation to name an abstract lifetime for all the elements:
subscript(position: Int) -> dependsOn(self.elements) Element
850
+
}
851
+
```
852
+
853
+
Note that a subscript setter reverses the dependence: `dependsOn(newValue -> .elements)`.
854
+
855
+
As before, when `Container` held a single element, it can temporarily take ownership of an element without narrowing its lifetime:
856
+
857
+
```swift
858
+
var c1: Container<Element>
859
+
{
860
+
let c2 = Container<Element>(element: c1[i])
861
+
c1[i] = c2[i] // OK: c2[i] can outlive c2
862
+
}
863
+
```
864
+
865
+
Now let's consider a `View` type, similar to `Span`, that provides access to a borrowed container's elements. The lifetime of the view depends on the container's storage. Therefore, the view depends on a *borrow* of the container. The container's elements, however, no longer depend on the container's storage once they have been copied. This can be expressed by giving the view an abstract lifetime for its elements, separate from the view's own lifetime:
866
+
867
+
```swift
868
+
@lifetime(elements)
869
+
structView<Element>: ~Escapable {
870
+
var storage: UnsafePointer<Element>
871
+
872
+
init(container: Container)
873
+
->dependsOn(container.elements-> .elements) // Copy the lifetime associated with container.elements
874
+
Self {...}
875
+
876
+
subscript(position: Int) -> dependsOn(self.elements) Element
// Require a borrow scope in the caller that borrows the container
887
+
var view: dependsOn(borrow self) View<Element> { get {...} }
888
+
889
+
var mutableView: dependsOn(borrow self) MutableView<Element> { mutatingget {...} }
890
+
}
891
+
```
892
+
893
+
Now an element can be copied out of a view `v2` and assigned to another view `v1` whose lifetime exceeds the borrow scope that constrains the lifetime of `v2`.
894
+
895
+
```swift
896
+
var c1: Container<Element>
897
+
let v1 = c1.mutableView
898
+
{
899
+
let v2 = c1.view// borrow scope for `v2`
900
+
v1[i] = v2[i] // OK: v2[i] can outlive v2
901
+
}
902
+
```
903
+
904
+
To see this more abstractly, rather than directly assigning, `v1[i] = v2[i]`, we can use a generic interface:
905
+
906
+
```swift
907
+
functransfer(from: Element, to: dependsOn(from) inoutElement) {
908
+
to = from
909
+
}
910
+
911
+
var c1: Container<Element>
912
+
let v1 = c1.mutableView
913
+
{
914
+
let v2 = c1.view// borrow scope for `v2`
915
+
transfer(from: v2[i], to: &v1[i]) // OK: v2[i] can outlive v2
916
+
}
917
+
```
918
+
919
+
### Protocol lifetime requirements
920
+
921
+
Value lifetimes are limited because they provide no way to refer to a lifetime without refering to a concrete type that the lifetime is associated with. To support generic interfaces, protocols need to refer to any lifetime requirements that can appear in interface.
922
+
923
+
Imagine that we want to access view through a protocol. To support returning elements that outlive the view, we need to require an `elements` lifetime requirement:
924
+
925
+
```swift
926
+
@lifetime(elements)
927
+
protocolViewProtocol {
928
+
subscript(position: Int) -> dependsOn(self.elements) Element
929
+
}
930
+
```
931
+
932
+
Let's return to View's initializer;
933
+
934
+
```swift
935
+
@lifetime(elements)
936
+
structView<Element>: ~Escapable {
937
+
init(container: borrowing Container) ->
938
+
// Copy the lifetime assoicate with container.elements
939
+
dependsOn(container.elements-> .elements)
940
+
Self {...}
941
+
}
942
+
```
943
+
944
+
This is not a useful initializer, because `View` should not be specific to a concrete `Container` type. Instead, we want `View` to be generic over any container that provides `elements` that can be copied out of the container's storage:
945
+
946
+
```swift
947
+
@lifetime(elements)
948
+
protocolElementStorage: ~Escapable {}
949
+
950
+
@lifetime(elements)
951
+
structView<Element>: ~Escapable {
952
+
init(storage: ElementStorage) ->
953
+
// Copy the lifetime assoicate with storage.elements
954
+
dependsOn(storage.elements-> .elements)
955
+
Self {...}
956
+
}
957
+
```
958
+
959
+
### Structural lifetime dependencies
960
+
961
+
A scoped dependence normally cannot escape the lexical scope of its source variable. It may, however, be convenient to escape the source of that dependence along with any values that dependent on its lifetime. This could be done by moving the ownership of the source into a structure that preserves any dependence relationships. A function that returns a nonescapable type cannot currently depend on the scope of a consuming parameter. But we could lift that restriction provided that the consumed argument is moved into the return value, and that the return type preserves any dependence on that value:
`arrayToOwnedSpan` creates a span with a scoped dependence on an array, then moves both the array and the span into an `OwnedSpan`, which can be returned from the function. This converts the original lexically scoped dependence into a structural dependence.
980
+
799
981
## Acknowledgements
800
982
801
983
Dima Galimzianov provided several examples for Future Directions.
0 commit comments