Skip to content

Commit c490a9f

Browse files
committed
[docs] update lifetime defaults to prioritize the same-type rule
1 parent a4ae171 commit c490a9f

File tree

1 file changed

+76
-74
lines changed

1 file changed

+76
-74
lines changed

docs/ReferenceGuides/LifetimeAnnotation.md

Lines changed: 76 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -18,58 +18,73 @@ The `@lifetime` annotation is enforced both in the body of the function and at e
1818

1919
## Default lifetimes
2020

21-
The Swift 6.2 compiler provided default `@_lifetime` behavior whenever it can do so without ambiguity. Often, despite ambiguity, an "obvious" default exists, but we wanted to introduce defaults slowly after developers have enough experience to inform discussion about them. This document tracks the current state of the implementation as it progresses from the original 6.2 implementation. Corresponding tests are in `test/Sema/lifetime_depend_infer.swift`; searching for "DEFAULT:" highlights the rules defined below...
21+
The Swift 6.2 compiler provided default `@_lifetime` behavior whenever it can do so without ambiguity. Often, despite ambiguity, an obvious default exists, but we wanted to introduce defaults slowly after developers have enough experience to inform discussion about them. This document tracks the current state of the implementation as it progresses from the original 6.2 implementation. Corresponding tests are in `test/Sema/lifetime_depend_infer.swift`; searching for "DEFAULT:" highlights the rules defined below...
2222

23-
### Single parameter default rule
23+
### Same-type default lifetime
2424

25-
Given a function or method that returns a non-Escapable result:
25+
Given a function declaration:
2626

27-
- Default to `@_lifetime(<scope> a)` for a `~Escapable` result on functions with a single parameter `a`.
27+
`func foo(..., a: A, ...) -> R { ... }`
2828

29-
- Default to `@_lifetime(<scope> self)` for a `~Escapable` result on methods with no parameters.
29+
Where `R: ~Escapable`, `A == R`, and `a` is not an `inout` parameter, default to `@_lifetime(copy a)`.
30+
For non-mutating methods, the same rule applies to implicit `Self` parameter.
3031

31-
| Type of parameter | default |
32-
| (`a` or `self`) | lifetime dependency |
33-
| ----------------- | ------------------------------ |
34-
| `Escapable` | `@_lifetime(borrow param)`[^1] |
35-
| `inout Escapable` | `@_lifetime(&param)`[^1] |
36-
| `~Escapable` | none[^2] |
32+
This handles the obvious cases in which both the parameter and result are `~Escapable`. For example:
3733

38-
[^1]: When the parameter is `BitwiseCopyable`, such as an integer or unsafe pointer, the single parameter default rule applies to function parameters but not to the implicit `self` parameter. Depending on a `BitwiseCopyable` value is a convenience for APIs that construct span-like values from an `UnsafePointer` passed as an argument. This creates a dependency on a local copy of the pointer variable with subtle semantics. User-defined `BitwiseCopyable` structs should generally avoid such subtle lifetime dependencies. If needed, the author of the data type should explicitly opt into them.
34+
```swift
35+
extension Span {
36+
/* DEFAULT: @_lifetime(copy self) */
37+
func extracting(droppingLast k: Int) -> Self { ... }
38+
}
39+
```
3940

40-
[^2]: When the single parameter is also `~Escapable`, the result must depend on it, but the dependency may either be scoped (`borrow` or `&`) or it may be copied (`copy`). `copy` is the obvious choice when the parameter and result are the same type, but it is not always correct. Furthermore, a lifetime dependency can only be copied from a generic type when result as the same generic type. This case is therefore handled by same-type default lifetime (discussed below) rather than as a default `@_lifetime` rule.
41+
#### Generic same-type default lifetime
4142

42-
Examples:
43+
The same-type default lifetime rule described above is convenient for nominal types but essential for generic type parameters.
4344

44-
```swift
45-
struct A: Escapable {
46-
let obj: AnyObject // ~BitwiseCopyable
47-
}
48-
struct NE: ~Escapable {...}
45+
Given a generic function declaration:
4946

50-
/* DEFAULT: @_lifetime(borrow a) */
51-
func oneParam_NEResult(a: A) -> NE
47+
`func foo<A, R>(..., a: A, ...) -> R { ... }`
5248

53-
/* DEFAULT: @_lifetime(&a) */
54-
func oneInoutParam_NEResult(a: inout A) -> NE
49+
The same-type default lifetime rule applies to the types in the function declaration's generic context just as it did for nominal types in the previous example. So, again, if type resolution determines `R: ~Escapable` and `A == R`, then `@_lifetime(copy a)` will be default.
5550

56-
extension A /* Self: Escapable */ {
57-
/* DEFAULT: @_lifetime(borrow self) */
58-
func noParam_NEResult() -> NE
51+
Unlike nominal types, the programmer is not allowed to explicitly declare a lifetime dependency, `@_lifetime(copy
52+
a)`, unless the argument and result types are equivalent (`A == R`). Copying a lifetime dependency from one value to another requires that both values are non-Escapable. Generic types are conditionally non-Escapable (their lifetime dependencies are type-erased), so type equivalence is the only way to ensure that both values are non-Escapable under the same conditions.
5953

60-
/* DEFAULT: @_lifetime(&self) */
61-
mutating func mutating_noParam_NEResult() -> NE
54+
Here we see how same-type lifetime requirement applies to type substitution and associated types:
55+
56+
```swift
57+
protocol P {
58+
associatedtype T: ~Escapable
59+
}
60+
61+
protocol Q {
62+
associatedtype U: ~Escapable
63+
}
64+
65+
struct S<A: P, B: Q> {
66+
/* OK: @_lifetime(copy a) is valid and default */
67+
func foo(a: A.T) -> B.U where A.T == B.U
6268
}
6369
```
6470

65-
### Implicit initializer and setter defaults
71+
Note that lifetime dependencies are resolved at function declaration time, which determines the function's type. The generic context at the point of function invocation is not considered. For example, the following declaration of `foo` is invalid, because it's argument and results types don't match at the point of declaration, even though the argument and result do have the same type when invoked inside `bar`:
6672

67-
An implicit setter of a `~Escapable` stored property defaults to `@_lifetime(self: copy self, copy newValue)`. This is always correct because the setter simply assigns the stored property to the newValue. Assigning a `~Escapable` variable copies the lifetime dependency.
73+
```swift
74+
struct S<T: ~Escapable, U: ~Escapable> {
75+
static func foo(a: T) -> U // ERROR: missing lifetime dependency
76+
}
6877

69-
Similarly, an implicit initializer of a non-Escapable struct defaults to `@_lifetime(self: copy arg)` if all of the initializer arguments are `~Escapable`. This is equivalent to assigning each `~Escapable` stored property. If, however, any initializer arguments are `Escapable`, then no default lifetime is provided unless it is the sole argument, in which case the single parameter rule applies.
78+
/* OK: @_lifetime(copy a) is valid and default */
79+
func bar<T: ~Escapable>(a: T) -> T {
80+
S<T, T>.foo(a: a) // The same-type rule is satisfied in this context, but 'foo's declaration is invalid.
81+
}
82+
```
7083

7184
### `inout` parameter default rule
7285

86+
The following `inout` parameter default rule directly follows from the general same-type default rule above. We descrive it as a separate rule here only because the behavior is important and would otherwise be nonobvious:
87+
7388
- Default to `@_lifetime(a: copy a)` for all `inout` parameters where `a` is `~Escapable`.
7489

7590
- Default to `@_lifetime(self: copy self)` on `mutating` methods where `self` is `~Escapable`.
@@ -122,63 +137,50 @@ extension NE /* Self: ~Escapable */ {
122137
}
123138
```
124139

125-
## Same-type default lifetime (unimplemented)
126-
127-
Given a function declaration:
128-
129-
`func foo(..., a: A, ...) -> R { ... }`
130-
131-
Where `R: ~Escapable` and `A == R`, default to `@_lifetime(copy a)`.
132-
For methods, the same rule applies to implicit `Self` parameter.
133-
134-
This handles the obvious cases in which both the parameter and result are `~Escapable`. For example:
135-
136-
```swift
137-
extension Span {
138-
/* DEFAULT: @_lifetime(copy self) */
139-
func extracting(droppingLast k: Int) -> Self { ... }
140-
}
141-
```
140+
### Single parameter default rule
142141

143-
### Generic same-type default lifetime (unimplemented)
142+
Given a function or method that returns a non-Escapable result, if that result's dependency does not have a same-type default:
144143

145-
The same-type default lifetime rule described above is convenient for nominal types but essential for generic type parameters.
144+
- Default to `@_lifetime(<scope> a)` for a `~Escapable` result on functions with a single parameter `a`.
146145

147-
Given a generic function declaration:
146+
- Default to `@_lifetime(<scope> self)` for a `~Escapable` result on methods with no parameters.
148147

149-
`func foo<A, R>(..., a: A, ...) -> R { ... }`
148+
| Type of parameter | default |
149+
| (`a` or `self`) | lifetime dependency |
150+
| ----------------- | ------------------------------ |
151+
| `Escapable` | `@_lifetime(borrow param)`[^1] |
152+
| `inout Escapable` | `@_lifetime(&param)`[^1] |
153+
| `~Escapable` | none[^2] |
150154

151-
The same-type default lifetime rule applies to the types in the function declaration's generic context just as it did for nominal types in the previous example. So, again, if type resolution determines `R: ~Escapable` and `A == R`, then `@_lifetime(copy a)` will be default.
155+
[^1]: When the parameter is `BitwiseCopyable`, such as an integer or unsafe pointer, the single parameter default rule applies to function parameters but not to the implicit `self` parameter. Depending on a `BitwiseCopyable` value is a convenience for APIs that construct span-like values from an `UnsafePointer` passed as an argument. This creates a dependency on a local copy of the pointer variable with subtle semantics. User-defined `BitwiseCopyable` structs should generally avoid such subtle lifetime dependencies. If needed, the author of the data type should explicitly opt into them.
152156

153-
Unlike nominal types, the programmer is not allowed to explicitly declare a lifetime dependency, `@_lifetime(copy
154-
a)`, unless the argument and result types are equivalent (`A == R`). Copying a lifetime dependency from one value to another requires that both values are non-Escapable. Generic types are conditionally non-Escapable (their lifetime dependencies are type-erased), so type equivalence is the only way to ensure that both values are non-Escapable under the same conditions.
157+
[^2]: When the single parameter is also `~Escapable`, the result must depend on it, but the dependency may either be scoped (`borrow` or `&`) or it may be copied (`copy`). `copy` is the obvious choice when the parameter and result are the same type, but it is not always correct. Furthermore, a lifetime dependency can only be copied from a generic type when result as the same generic type. This case is therefore handled by same-type default lifetime (discussed below) rather than as a default `@_lifetime` rule.
155158

156-
Here we see how same-type lifetime requirement applies to type substitution and associated types:
159+
Examples:
157160

158161
```swift
159-
protocol P {
160-
associatedtype T: ~Escapable
162+
struct A: Escapable {
163+
let obj: AnyObject // ~BitwiseCopyable
161164
}
165+
struct NE: ~Escapable {...}
162166

163-
protocol Q {
164-
associatedtype U: ~Escapable
165-
}
167+
/* DEFAULT: @_lifetime(borrow a) */
168+
func oneParam_NEResult(a: A) -> NE
166169

167-
struct S<A: P, B: Q> {
168-
/* OK: @_lifetime(copy a) is valid and default */
169-
func foo(a: A.T) -> B.U where A.T == B.U
170+
/* DEFAULT: @_lifetime(&a) */
171+
func oneInoutParam_NEResult(a: inout A) -> NE
172+
173+
extension A /* Self: Escapable */ {
174+
/* DEFAULT: @_lifetime(borrow self) */
175+
func noParam_NEResult() -> NE
176+
177+
/* DEFAULT: @_lifetime(&self) */
178+
mutating func mutating_noParam_NEResult() -> NE
170179
}
171180
```
172181

173-
Note that lifetime dependencies are resolved at function declaration time, which determines the function's type. The generic context at the point of function invocation is not considered. For example, the following declaration of `foo` is invalid, because it's argument and results types don't match at the point of declaraion, even though the argument and result do have the same type when invoked inside `bar`:
182+
### Implicit initializer and setter defaults
174183

175-
```swift
176-
struct S<T: ~Escapable, U: ~Escapable> {
177-
static func foo(a: T) -> U // ERROR: missing lifetime dependency
178-
}
184+
An implicit setter of a `~Escapable` stored property defaults to `@_lifetime(self: copy self, copy newValue)`. This is always correct because the setter simply assigns the stored property to the newValue. Assigning a `~Escapable` variable copies the lifetime dependency.
179185

180-
/* OK: @_lifetime(copy a) is valid and default */
181-
func bar<T: ~Escapable>(a: T) -> T {
182-
S<T, T>.foo(a: a) // The same-type rule is satisfied in this context, but 'foo's declaration is invalid.
183-
}
184-
```
186+
Similarly, an implicit initializer of a non-Escapable struct defaults to `@_lifetime(self: copy arg)` if all of the initializer arguments are `~Escapable`. This is equivalent to assigning each `~Escapable` stored property. If, however, any initializer arguments are `Escapable`, then no default lifetime is provided unless it is the sole argument, in which case the single parameter rule applies.

0 commit comments

Comments
 (0)