|
| 1 | +# @_lifetime annotation |
| 2 | + |
| 3 | +## Introduction |
| 4 | + |
| 5 | +`@_lifetime` annotations are now available under `-enable-experimental-features Lifetimes`. The feature proposal is documented in [PR: Lifetime dependencies #2750](https://github.com/swiftlang/swift-evolution/pull/2750). |
| 6 | + |
| 7 | +To summarize the basic syntax: functions require a `@_lifetime` annotation when they return a non-Escapable type, either as the function result, or as an `inout` parameter. The annotation syntax pattern is `@_lifetime(target: <scope> source)` where `target` is a function output and `source` is an input. If `target:` is omitted, then it is assumed to be the function's result. |
| 8 | + |
| 9 | +`<scope> ::= [borrow|&|copy]` |
| 10 | + |
| 11 | +`borrow` creates a new borrow scope that guarantees exclusive read access to the caller's `source` argument over all uses of `target`. |
| 12 | + |
| 13 | +`&` creates a new borrow scope that guarantees exclusive write access to the caller's `source` argument over all uses of `target`. |
| 14 | + |
| 15 | +`copy` copies the lifetime constraints on the caller's `source` argument to `target`. |
| 16 | + |
| 17 | +The `@lifetime` annotation is enforced both in the body of the function and at each call site. For both `borrow` and `&` scoped dependencies, the function's implementation guarantees that `target` is valid as long as `source` is alive, and each caller of the function guarantees that `source` will outlive `target`. For `copy` dependencies, the function's implementation guarantees that all constraints on `target` are copied from `source`, and the caller propagates all lifetime constraints on `source` to all uses of `target`. |
| 18 | + |
| 19 | +## Default lifetimes |
| 20 | + |
| 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... |
| 22 | + |
| 23 | +### Single parameter default rule |
| 24 | + |
| 25 | +Given a function or method that returns a non-Escapable result: |
| 26 | + |
| 27 | +- Default to `@_lifetime(<scope> a)` for a `~Escapable` result on functions with a single parameter `a`. |
| 28 | + |
| 29 | +- Default to `@_lifetime(<scope> self)` for a `~Escapable` result on methods with no parameters. |
| 30 | + |
| 31 | +| Type of parameter | default | |
| 32 | +| (`a` or `self`) | lifetime dependency | |
| 33 | +| ----------------- | ------------------------------ | |
| 34 | +| `Escapable` | `@_lifetime(borrow param)`[^1] | |
| 35 | +| `inout Escapable` | `@_lifetime(¶m)`[^1] | |
| 36 | +| `~Escapable` | none[^2] | |
| 37 | + |
| 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. |
| 39 | + |
| 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 | + |
| 42 | +Examples: |
| 43 | + |
| 44 | +```swift |
| 45 | +struct A: Escapable { |
| 46 | + let obj: AnyObject // ~BitwiseCopyable |
| 47 | +} |
| 48 | +struct NE: ~Escapable {...} |
| 49 | + |
| 50 | +/* DEFAULT: @_lifetime(borrow a) */ |
| 51 | +func oneParam_NEResult(a: A) -> NE |
| 52 | + |
| 53 | +/* DEFAULT: @_lifetime(&a) */ |
| 54 | +func oneInoutParam_NEResult(a: inout A) -> NE |
| 55 | + |
| 56 | +extension A /* Self: Escapable */ { |
| 57 | + /* DEFAULT: @_lifetime(borrow self) */ |
| 58 | + func noParam_NEResult() -> NE |
| 59 | + |
| 60 | + /* DEFAULT: @_lifetime(&self) */ |
| 61 | + mutating func mutating_noParam_NEResult() -> NE |
| 62 | +} |
| 63 | +``` |
| 64 | + |
| 65 | +### Implicit initializer and setter defaults |
| 66 | + |
| 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. |
| 68 | + |
| 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. |
| 70 | + |
| 71 | +### `inout` parameter default rule |
| 72 | + |
| 73 | +- Default to `@_lifetime(a: copy a)` for all `inout` parameters where `a` is `~Escapable`. |
| 74 | + |
| 75 | +- Default to `@_lifetime(self: copy self)` on `mutating` methods where `self` is `~Escapable`. |
| 76 | + |
| 77 | +#### Examples |
| 78 | + |
| 79 | +```swift |
| 80 | +struct A: Escapable { |
| 81 | + let obj: AnyObject // ~BitwiseCopyable |
| 82 | +} |
| 83 | +struct NE: ~Escapable {...} |
| 84 | + |
| 85 | +/* DEFAULT: @_lifetime(a: copy a) */ |
| 86 | +func inoutNEParam_void(_: inout NE) -> () |
| 87 | + |
| 88 | +/* DEFAULT: @_lifetime(a: copy a) */ |
| 89 | +/* DEFAULT: @_lifetime(b: copy b) */ |
| 90 | +func inoutNEParam_inoutNEParam_void(a: inout NE, b: inout NE) -> () |
| 91 | + |
| 92 | +/* DEFAULT: @_lifetime(ne: copy ne) */ |
| 93 | +@_lifetime(&ne) |
| 94 | +func inoutNEParam_NEResult(ne: inout NE) -> NE |
| 95 | + |
| 96 | +extension A /* Self: Escapable */ { |
| 97 | + /* DEFAULT: @_lifetime(ne: copy NE) */ |
| 98 | + func inoutNEParam_void(a: inout ) -> () |
| 99 | + |
| 100 | + /* DEFAULT: @_lifetime(ne: copy NE) */ |
| 101 | + mutating func mutating_inoutNEParam_void() -> () |
| 102 | + |
| 103 | + /* DEFAULT: @_lifetime(ne: copy NE) */ |
| 104 | + @_lifetime(&self) |
| 105 | + func inoutNEParam_NEResult(ne: inout NE) -> NE |
| 106 | +} |
| 107 | + |
| 108 | +extension NE /* Self: ~Escapable */ { |
| 109 | + /* DEFAULT: @_lifetime(self: copy self) */ |
| 110 | + mutating func mutating_noParam_void() -> () |
| 111 | + |
| 112 | + /* DEFAULT: @_lifetime(self: copy self) */ |
| 113 | + mutating func mutating_oneParam_void(_: NE) -> () |
| 114 | + |
| 115 | + /* DEFAULT: @_lifetime(self: copy self) */ |
| 116 | + /* DEFAULT: @_lifetime(ne: copy ne) */ |
| 117 | + mutating func mutating_inoutParam_void(ne: inout NE) -> () |
| 118 | + |
| 119 | + /* DEFAULT: @_lifetime(self: copy self) */ |
| 120 | + @_lifetime(&self) |
| 121 | + mutating func mutating_noParam_NEResult() -> NE |
| 122 | +} |
| 123 | +``` |
| 124 | + |
| 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 | +``` |
| 142 | + |
| 143 | +### Generic same-type default lifetime (unimplemented) |
| 144 | + |
| 145 | +The same-type default lifetime rule described above is convenient for nominal types but essential for generic type parameters. |
| 146 | + |
| 147 | +Given a generic function declaration: |
| 148 | + |
| 149 | +`func foo<A, R>(..., a: A, ...) -> R { ... }` |
| 150 | + |
| 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. |
| 152 | + |
| 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. |
| 155 | + |
| 156 | +Here we see how same-type lifetime requirement applies to type substitution and associated types: |
| 157 | + |
| 158 | +```swift |
| 159 | +protocol P { |
| 160 | + associatedtype T: ~Escapable |
| 161 | +} |
| 162 | + |
| 163 | +protocol Q { |
| 164 | + associatedtype U: ~Escapable |
| 165 | +} |
| 166 | + |
| 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 | +} |
| 171 | +``` |
| 172 | + |
| 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`: |
| 174 | + |
| 175 | +```swift |
| 176 | +struct S<T: ~Escapable, U: ~Escapable> { |
| 177 | + static func foo(a: T) -> U // ERROR: missing lifetime dependency |
| 178 | +} |
| 179 | + |
| 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 | +``` |
0 commit comments