Skip to content

Commit 2fd52e1

Browse files
committed
Additions to Future directions
- Value component lifetime - Abstract lifetime components - Protocol lifetime requirements - Structural lifetime dependencies
1 parent 3b6f859 commit 2fd52e1

File tree

1 file changed

+182
-0
lines changed

1 file changed

+182
-0
lines changed

proposals/NNNN-lifetime-dependency.md

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -796,6 +796,188 @@ This could be exposed as an alternate spelling if there were sufficient demand.
796796
func f(arg1: Type1, arg2: Type2, arg3: Type3) -> dependsOn(0) ReturnType
797797
```
798798

799+
### Value component lifetime
800+
801+
In the current design, aggregating multiple values merges their scopes.
802+
803+
```swift
804+
struct Container<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:
824+
825+
```swift
826+
struct Container<Element>: ~Escapable {
827+
@lifetime
828+
var a: /*dependsOn(self.a)*/ Element
829+
@lifetime
830+
var b: /*dependsOn(self.b)*/ Element
831+
832+
init(a: Element, b: Element) -> dependsOn(a -> .a, b -> .b) Self {...}
833+
}
834+
```
835+
836+
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:
841+
842+
```swift
843+
@lifetime(elements)
844+
struct Container<Element>: ~Escapable {
845+
var storage: UnsafeMutablePointer<Element>
846+
847+
init(element: Element) -> dependsOn(element -> .elements) Self {...}
848+
849+
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+
struct View<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
877+
}
878+
879+
@lifetime(elements)
880+
struct MutableView<Element>: ~Escapable, ~Copyable {
881+
var storage: UnsafeMutablePointer<Element>
882+
//...
883+
}
884+
885+
extension Container {
886+
// 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> { mutating get {...} }
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+
func transfer(from: Element, to: dependsOn(from) inout Element) {
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+
protocol ViewProtocol {
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+
struct View<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+
protocol ElementStorage: ~Escapable {}
949+
950+
@lifetime(elements)
951+
struct View<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:
962+
963+
```swift
964+
struct OwnedSpan<T>: ~Copyable & ~Escapable{
965+
let owner: any ~Copyable
966+
let span: dependsOn(scope owner) Span<T>
967+
968+
init(owner: consuming any ~Copyable, span: dependsOn(scope owner) Span<T>) -> dependsOn(scoped owner) Self {
969+
self.owner = owner
970+
self.span = span
971+
}
972+
}
973+
974+
func arrayToOwnedSpan<T>(a: consuming [T]) -> OwnedSpan<T> {
975+
OwnedSpan(owner: a, span: a.span())
976+
}
977+
```
978+
979+
`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+
799981
## Acknowledgements
800982

801983
Dima Galimzianov provided several examples for Future Directions.

0 commit comments

Comments
 (0)