Skip to content

Commit 4152b2e

Browse files
authored
Merge pull request swiftlang#9 from atrick/lifetime-dependency
@Lifetime annotation + simplified implicit lifetime dependencies
2 parents 699d547 + ac43daa commit 4152b2e

File tree

1 file changed

+103
-48
lines changed

1 file changed

+103
-48
lines changed

proposals/NNNN-lifetime-dependency.md

Lines changed: 103 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,16 @@ This is a key requirement for the `Span` type (previously called `BufferView`) b
3838
**Edited** (June 9, 2024):
3939

4040
- New section: Immortal requirements
41-
- New alternative considered: Initializer syntax
4241
- New alternative considered: dependsOn(unchecked) to disable lifetime dependence checking
4342
- Updated future direction: component lifetime syntax
4443
- New example: Escapable properties in a nonescapable type
4544

45+
**Edited** (July 31, 2024)
46+
47+
- New alternative considered: @lifetime annotation
48+
- New alternative considered: where clause
49+
- Simplified implicit lifetime dependencies and added same-type rule
50+
4651
#### See Also
4752

4853
* [Forum discussion of Non-Escapable Types and Lifetime Dependency](https://forums.swift.org/t/pitch-non-escapable-types-and-lifetime-dependency)
@@ -253,12 +258,11 @@ init(arg: <parameter-convention> ArgType) -> dependsOn(arg) Self
253258

254259
### Implicit Lifetime Dependencies
255260

256-
The syntax above allows developers to explicitly annotate lifetime dependencies in their code.
257-
But because the possibilities are limited, we can usually allow the compiler to infer a suitable dependency.
258-
The detailed rules are below, but generally we require that the return type be nonescapable and that there be one “obvious” source for the dependency.
261+
The syntax above allows developers to explicitly annotate lifetime dependencies in their code. But because the possibilities are limited, we can usually allow the compiler to infer a suitable dependency. The detailed rules are below, but generally we require that the return type be nonescapable and that there be an “obvious” source for the dependency.
259262

260-
In particular, we can infer a lifetime dependency on `self` for any method that returns a nonescapable value.
261-
As above, the details vary depending on whether `self` is escapable or nonescapable:
263+
#### Self dependence
264+
265+
We can infer a lifetime dependency on `self` for any method that returns a nonescapable value. As above, the details vary depending on whether `self` is escapable or nonescapable:
262266

263267
```swift
264268
struct NonescapableType: ~Escapable { ... }
@@ -282,25 +286,21 @@ struct NEStruct: ~Escapable {
282286
}
283287
```
284288

285-
For free or static functions or initializers, we can infer a lifetime dependency when the return value is nonescapable and there is only one obvious argument that can serve as the source of the dependency.
286-
For example:
289+
#### Same-type dependence
287290

288-
```swift
289-
struct NEType: ~Escapable { ... }
291+
For any function or method that returns a nonescapable type, we infer a copied lifetime dependency on all parameters of the same type.
290292

291-
// If there is only one argument with an explicit parameter convention:
292-
func f(..., arg1: borrowing Type1, ...) -> /* dependsOn(arg1) */ NEType
293+
`func foo<T: ~Escapable, U: ~Escapable, R: ~Escapable>(x: T, y: U) -> R { ... }`
293294

294-
// Or there is only one argument that is `~Escapable`:
295-
func g(..., arg2: NEType, ...) -> /* dependsOn(arg2) */ NEType
295+
implies:
296296

297-
// If there are multiple possible arguments that we might depend
298-
// on, we require an explicit dependency:
299-
// 🛑 Cannot infer lifetime dependency since `arg1` and `arg2` are both candidates
300-
func g(... arg1: borrowing Type1, arg2: NEType, ...) -> NEType
297+
```
298+
-> dependsOn(x) where R == T
299+
-> dependsOn(y) where R == U
300+
-> dependsOn(x, y) where R == T == U
301301
```
302302

303-
We expect these implicit inferences to cover most cases, with the explicit form only occasionally being necessary in practice.
303+
This is particularly helpful for Generic APIs. With this rule, indicating that a generic parameter is `~Escapable` should usually be sufficient to infer the correct lifetime dependence.
304304

305305
### Dependent parameters
306306

@@ -668,20 +668,35 @@ The implications of mutation modifiers and argument type on the resulting lifeti
668668

669669
### Inference Rules
670670

671-
If there is no explicit lifetime dependency, we will automatically infer one according to the following rules:
671+
If there is no explicit lifetime dependency on the nonescapable result of a method or function, we will attempt to infer dependencies automatically according the following rules:
672672

673-
**For methods where the return value is nonescapable**, we will infer a dependency against self, depending on the mutation type of the function.
674-
Note that this is not affected by the presence, type, or modifier of any other arguments to the method.
673+
1. For methods where the return value is nonescapable, we will infer a dependency against `self`. If `self` is nonescapable, then we infer a copying dependency. If `self` is escapable, and the method is `borrowing` or `mutating`, then we infer a scoped dependency.
675674

676-
**For a free or static functions or initializers with at least one argument,** we will infer a lifetime dependency when the return value is nonescapable and exactly one argument that satisfies any of the following:
677-
- is nonescapable, or
678-
- is non-BitwiseCopyable and has an explicit `borrowing`, or `inout` convention
675+
2. For methods, functions, and initializers where the return value is nonescapable, we infer a copied lifetime dependency on all parameters of the same (nonescapable) type, including the implicit `self` parameter.
679676

680-
In this case, the compiler will infer a dependency on the unique argument identified by these conditions.
677+
3. For functions and initializers that have a nonescapable return value and a single parameter, we infer dependence on that parameter. If the parameter is nonescapable, then we infer a copying dependency; otherwise, we infer a scoped dependency.
678+
679+
For all inference rules, the type of dependence is the same as an explicit `dependsOn(argument)` on the same argument without any `scoped` qualifier based on the argument's type.
681680

682681
**In no other case** will a function, method, or initializer implicitly gain a lifetime dependency.
683682
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.
684683

684+
We infer dependencies according to all applicable rules. Here, both rule #1 and #2 apply:
685+
686+
```
687+
struct NE: ~Escapable { ... }
688+
struct E {
689+
func foo(ne: NE) -> /* dependsOn(self, ne) */ NE
690+
}
691+
```
692+
693+
Here, both rule #2 and #3 apply:
694+
695+
```
696+
struct NE {
697+
init(ne: NE) -> /* dependsOn(ne) */ Self
698+
}
699+
```
685700

686701
### Dependency semantics by example
687702

@@ -851,27 +866,6 @@ Removing a lifetime dependency constraint only affects existing source code in t
851866

852867
## Alternatives considered
853868

854-
### Initializer syntax: result vs. inout syntax
855-
856-
The programming model for initializers is that they return `self` (with an implicit return statement):
857-
858-
`init(arg: ArgType) -> dependsOn(arg) Self`
859-
860-
But some people have criticized this syntax. They prefer to think of an initializer as mutating `self`, which would be
861-
spelled:
862-
863-
`dependsOn(self: arg) init(arg: ArgType)`
864-
865-
We could adopt either or both of these options.
866-
867-
In a future with component lifetimes the syntax would look like either:
868-
869-
`init(arg1: Element, arg2: Element) -> dependsOn(a: arg1, b: arg2) Self {...}`
870-
871-
or
872-
873-
`dependsOn(self.a: arg1, self.b: arg2) init(arg1: Element, arg2: Element) -> Self {...}`
874-
875869
### Different Position
876870

877871
We propose above putting the annotation on the return value, which we believe matches the intuition that the method or property is producing this lifetime dependence alongside the returned value.
@@ -900,14 +894,75 @@ The currently proposed `dependsOn` spelling was chosen to convey the direction o
900894

901895
func foo(a: A, b: B) -> dependsOn(a) R
902896

903-
This does, however, introduce compound keyword. Alternatively, we could use a simpler `lifetime` keyword, which better matches the feature description. The general syntax would then be:
897+
This does, however, introduce a keyword with a compound name. Alternatively, we could use a simpler `lifetime` keyword, which better matches the feature description. The general syntax would then be:
904898

905899
> **lifetime**(*target*: [scoped] *source*)
906900
907901
APIs with ambiguous depenencies would then typically be spelled:
908902

909903
func foo(a: A, b: B) -> lifetime(a) R
910904

905+
### @lifetime annotation
906+
907+
Instead of committing to a final, lightweight syntax, we can start with a single `@lifetime` annotation. It would take this form:
908+
909+
```
910+
@lifetime(target1.component: [copy|mutate|borrow] source1.component)
911+
@lifetime(target2.component: [copy|mutate|borrow] source2.component)
912+
func foo(...)
913+
```
914+
915+
`target` can be `self`, any parameter name, or, most commonly an empty string which implies the function result. `source` can be `self` or any parameter name. The most common usage would be:
916+
917+
```
918+
@lifetime(copy arg)
919+
func foo(arg: Arg1) -> R {}
920+
```
921+
922+
The `.component` qualifier is only relevant once we have component lifetimes. See the "Component lifetime" section below.
923+
924+
An annotation has some advantages over a lighter-weight type modifier sytax:
925+
926+
The `@` sigil is helpful to distinguish lifetime dependence information from regular function syntax.
927+
928+
A position-independent annotation has an advantage that the fully expressive syntax is more self-evident. This makes it easier to educate reviewers about what is possible with the syntax.
929+
930+
The type modifier can occur in any type position within a function signature, in including before the `func` keyword for the 'self' type. This has potential readability problems when it comes to more complicated cases. Nested parentheses (`dependsOn(...)`) that can occur anywhere in the signature are visually confusing.
931+
932+
In the future, the single `@lifetime` annotation could be a useful modifier for other kinds declarations such as types and properties:
933+
934+
```
935+
// Allow two components to have distinct lifetimes...
936+
struct Pair<T: ~Escapable> {
937+
@lifetime
938+
var x: T
939+
940+
@lifetime
941+
var y: T
942+
}
943+
944+
// Allow two components to have dependent lifetimes...
945+
struct Node: ~Escapable {
946+
@lifetime
947+
var parent: Node
948+
949+
@lifetime(parent)
950+
var child: Node
951+
}
952+
953+
// Declare an abstract lifetime and alias it with another lifetime.
954+
@lifetime(elements: storage.elements)
955+
struct Container {
956+
var storage: Storage
957+
}
958+
```
959+
960+
### `where` clause
961+
962+
Some have advocated for a `where` clause on the function declaration. The function name could stand-in for its result, and directionality could be indicated with a comparison operator:
963+
964+
`func foo(arg: Arg) -> R where lifetime(foo) < lifetime([copy|borrow|mutate] arg)`
965+
911966
### dependsOn(unchecked) to disable lifetime dependence checking
912967

913968
A `dependsOn(unchecked)` annotation could allow programmers to disable lifetime dependence checking for a function result or argument. For example, the programmer may want to compose a nonescapable result from an immortal value that isn't visible to the compiler:

0 commit comments

Comments
 (0)