Skip to content

Commit eed1b05

Browse files
committed
Overhaul semantics discussion
We need to separate description of the dependency itself -- which places a bound on when particular objects can be destroyed -- from the syntax. In particular, the syntax specifies a relationship between two objects, but that relationship is not always a lifetime dependency itself (because of "copied" dependencies).
1 parent 6ebcdb4 commit eed1b05

File tree

1 file changed

+69
-50
lines changed

1 file changed

+69
-50
lines changed

proposals/NNNN-lifetime-dependency.md

Lines changed: 69 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -238,8 +238,8 @@ struct EscStruct {
238238
borrowing func f2(...) -> /* @dependsOn(self) */ NonescapableType
239239
mutating func f3(...) -> /* @dependsOn(self) */ NonescapableType
240240

241-
// Note: no lifetime dependency is inferred for a consuming method
242-
// on an `Escapable` type, since there is no legal option
241+
// 🛑 Error: there is no valid lifetime dependency for
242+
// a consuming method on an `Escapable` type
243243
consuming func f4(...) -> NonescapableType
244244
}
245245

@@ -275,46 +275,101 @@ We expect these implicit inferences to cover most cases, with the explicit form
275275

276276
## Detailed design
277277

278+
### Relation to ~Escapable
279+
280+
The lifetime dependencies described in this document can be applied only to `~Escapable` return values.
281+
Further, any return value that is `~Escapable` must have a lifetime dependency.
282+
In particular, this implies that the initializer for a non-escapable type must have at least one argument.
283+
284+
```swift
285+
struct S: ~Escapable {
286+
init() {} // 🛑 Error: ~Escapable return type must have lifetime dependency
287+
}
288+
```
289+
290+
### Basic Semantics
291+
292+
A lifetime dependency annotation creates a *lifetime dependency* between a *dependent value* and a *source value*.
293+
This relationship obeys the following requirements:
294+
295+
* The dependent value must be nonescapable.
296+
297+
* The dependent value's lifetime must not be longer than that of the source value.
298+
299+
* The dependent value is treated as an ongoing access to the source value.
300+
Following Swift's usual exclusivity rules, the source value may not be mutated during the lifetime of the dependent value;
301+
if the access is a mutating access, the source value is further prohibited from being accessed at all during the lifetime of the dependent value.
302+
303+
The compiler must issue a diagnostic if any of the above cannot be satisfied.
304+
278305
### Grammar
279306

280307
This new syntax adds an optional lifetime modifier just before the return type.
281308
This modifies *function-result* in the Swift grammar as follows:
282309

283310
>
284311
> *function-signature**parameter-clause* **`async`***?* **`throws`***?* *function-result**?* \
285-
> *function-signature**parameter-clause* **`async`***?* **`rethrows`** *function-result**?*
286-
> *function-result***`->`** *attributes?* *lifetime-modifiers?* *type*
287-
> *lifetime-modifiers* **`->`** *lifetime-modifier* *lifetime-modifiers?*
288-
> *lifetime-modifier* **`->`** **`@dependsOn`** **`(`** *lifetime-dependency* **`)`**
289-
> *lifetime-dependency* **`->`** **`self`** | *local-parameter-name* | **`scoped self`** | **`scoped`** *local-parameter-name*
312+
> *function-signature**parameter-clause* **`async`***?* **`rethrows`** *function-result**?* \
313+
> *function-result***`->`** *attributes?* *lifetime-modifiers?* *type* \
314+
> *lifetime-modifiers* **`->`** *lifetime-modifier* *lifetime-modifiers?* \
315+
> *lifetime-modifier* **`->`** **`@dependsOn`** **`(`** *lifetime-dependent* **`)`** \
316+
> *lifetime-dependent* **`->`** **`self`** | *local-parameter-name* | **`scoped self`** | **`scoped`** *local-parameter-name*
290317
>
291318
292-
Here, the *lifetime-dependency* argument to the lifetime modifier must be one of the following:
319+
The *lifetime-dependent* argument to the lifetime modifier is one of the following:
293320

294321
* *local-parameter-name*: the local name of one of the function parameters, or
295322
* the token **`self`**, or
296323
* either of the above preceded by the **`scoped`** keyword
297324

325+
This modifier creates a lifetime dependency with the return value used as the dependent value.
326+
The return value must be nonescapable.
327+
328+
The source value of the resulting dependency can vary.
329+
In some cases, the source value will be the named parameter or `self` directly.
330+
However, if the corresponding named parameter or `self` is nonescapable, then that value will itself have an existing lifetime dependency and thus the new dependency might "copy" the source of that existing dependency.
331+
332+
The following table summarizes the possibilities, which depend on the type and mutation modifier of the argument or `self` and the existence of the `scoped` keyword.
333+
Here, "scoped" indicates that the dependent gains a direct lifetime dependency on the named parameter or `self` and "copied" indicates that the dependent gains a lifetime dependency on the source of an existing dependency:
334+
335+
| mutation modifier | argument type | without `scoped` | with `scoped` |
336+
| ------------------ | ------------- | ---------------- | ------------- |
337+
| borrowed | escapable | scoped | scoped |
338+
| inout or mutating | escapable | scoped | scoped |
339+
| consuming | escapable | Illegal | Illegal |
340+
| borrowed | nonescapable | copied | scoped |
341+
| inout or mutating | nonescapable | copied | scoped |
342+
| consuming | nonescapable | copied | Illegal |
343+
344+
Two observations may help in understanding the table above:
345+
* An escapable argument cannot have a pre-existing lifetime dependency, so copying is never possible in those cases.
346+
* A consumed argument cannot be the source of a lifetime dependency that will outlive the function call, so only copying is legal in that case.
347+
348+
**Note**: In practice, the `scoped` modifier keyword is likely to be only rarely used. The rules above were designed to support the known use cases without requiring such a modifier.
349+
298350
#### Initializers
299351

300-
Initializers can have arguments, and there are cases where users will want to specify a lifetime dependency between one or more arguments and the constructed value.
301-
We propose allowing initializers to write out an explicit return clause for this case.
352+
Since nonescapable values cannot be returned without a lifetime dependency,
353+
initializers for such types must specify a lifetime dependency on one or more arguments.
354+
We propose allowing initializers to write out an explicit return clause for this case, which permits the use of the same syntax as functions or methods.
302355
The return type must be exactly the token `Self` or the token sequence `Self?` in the case of a failable initializer:
303356

304357
```swift
305358
struct S {
306359
init(arg1: Type1) -> @dependsOn(arg1) Self
307-
init?(arg2: Type2) -> @dependsOn(scoped arg2) Self?
360+
init?(arg2: Type2) -> @dependsOn(arg2) Self?
308361
}
309362
```
310363

311364
> Grammar of an initializer declaration:
312365
>
313366
> *initializer-declaration**initializer-head* *generic-parameter-clause?* *parameter-clause* **`async`***?* **`throws`***?* *initializer-lifetime-modifier?* *generic-where-clause?* *initializer-body* \
314-
> *initializer-declaration**initializer-head* *generic-parameter-clause?* *parameter-clause* **`async`***?* **`rethrows`** *initializer-lifetime-modifier?* *generic-where-clause?* *initializer-body*
367+
> *initializer-declaration**initializer-head* *generic-parameter-clause?* *parameter-clause* **`async`***?* **`rethrows`** *initializer-lifetime-modifier?* *generic-where-clause?* *initializer-body* \
315368
> *initializer-lifetime-modifier*`**->**` *lifetime-modifiers* ** **`Self`** \
316369
> *initializer-lifetime-modifier*`**->**` *lifetime-modifiers* ** **`Self?`**
317370
371+
The implications of mutation modifiers and argument type on the resulting lifetime dependency exactly follow the rules above for functions and methods.
372+
318373
### Inference Rules
319374

320375
If there is no explicit lifetime dependency, we will automatically infer one according to the following rules:
@@ -326,55 +381,19 @@ Note that this is not affected by the presence, type, or modifier of any other a
326381

327382
* the return type is `~Escapable`,
328383
* there is exactly one argument that satisfies any of the following:
329-
** is either `~Copyable` or `~Escapable`
330-
** is `Escapable` and `Copyable` and has an explicit `borrowing`, `consuming`, or `inout` convention specified
384+
- is either `~Copyable` or `~Escapable`
385+
- has an explicit `borrowing`, `consuming`, or `inout` convention specified
331386

332387
In this case, the compiler will infer a dependency on the unique argument identified by this last set of conditions.
333388

334389
**In no other case** will a function, method, or initializer implicitly gain a lifetime dependency.
335390
If a function, method, or initializer has a `~Escapable` return type, 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.
336391

337-
### Semantics
338-
339-
The previous sections detail how lifetime dependency between the return value of a function or method and a function argument, method argument, or `self` can be explicitly declared or implicitly inferred by the compiler.
340-
341-
When the dependency involves a function, method, or initializer argument,
342-
if the corresponding argument is `borrowing` or `inout` then we can refer to that argument as the *source* of the dependency, and the return value then has a *scoped lifetime dependency* on the source.
343-
When this occurs, the compiler may shorten the lifetime of the return value or extend the lifetime of the source value within the existing language rules in order to satisfy the requirements below.
344-
Further, the compiler will issue diagnostics if these requirements cannot be satisfied:
345-
346-
* The return value must be destroyed before the source value.
347-
This can be obstructed if there are other factors (such as nested scopes, function returns, or closure captures) that contradict the lifetime dependency.
348-
* For a borrowing argument, the source value cannot be mutated before the return value is destroyed.
349-
* For an inout argument, the source value is accessed or mutated before the return value is destroyed.
350-
351-
The rules above apply with the obvious modifications for a method that explicitly or implicitly has a lifetime dependency between the return value and `self`.
352-
353-
If the `lifetime-kind` is `consume` or `copy`, then the return value from the function or method gains the same lifetime dependency as the function argument, method argument, or `self`.
354-
In this case, we’ll refer to the argument or `self` as the *original* value.
355-
In this case, the original value must itself must be `~Escapable`, and must in turn have a borrow or mutate lifetime dependency on some other source value.
356-
The return value will then have a borrow or mutate lifetime dependency on that same source value that will be enforced by the compiler as above.
357-
358-
### Relation to ~Escapable
359-
360-
The lifetime dependencies described in this document can be applied only to `~Escapable` return values.
361-
Further, any return value that is `~Escapable` must have a lifetime dependency.
362-
In particular, this implies that the initializer for a non-escapable type must have at least one argument.
363-
364-
```swift
365-
struct S: ~Escapable {
366-
init() {} // 🛑 Error: ~Escapable return type must have lifetime dependency
367-
}
368-
```
369-
370392
## Source compatibility
371393

372394
Everything discussed here is additive to the existing Swift grammar and type system.
373395
It has no effect on existing code.
374396

375-
The tokens `-> dependsOn` in a function declaration might indicate the beginning of a borrowing lifetime annotation or could indicate that the function returns an existing type called `dependsOn`.
376-
This ambiguity can be fully resolved in the parser by looking for an open parenthesis `(` after the `dependsOn` token.
377-
378397
## Effect on ABI stability
379398

380399
Lifetime dependency annotations may affect how values are passed into functions, and thus adding or removing one of these annotations should generally be expected to affect the ABI.

0 commit comments

Comments
 (0)