-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
'fn lifetime ascription #1847
Closed
Closed
'fn lifetime ascription #1847
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
fb45c89
'fn lifetime ascription
llogiq 4f9b2c1
Add unresolved question of `break 'fn ..`
Ericson2314 041dc18
Merge pull request #1 from Ericson2314/fn-lifetime
llogiq 259ef69
Put example in function body to make clear 'fn is bound
Ericson2314 2949e53
One sentance per line
Ericson2314 6b28405
Remove section descriptions for filled out sections
Ericson2314 5b4d120
Rewrite the detailed design section in greater detail, including issu…
Ericson2314 3a8a0ac
Merge pull request #2 from Ericson2314/fn-lifetime
llogiq 4804d14
(hopefully) clear up example, fix label syntax, ask for unsoundness
llogiq File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
- Feature Name: fn lifetime | ||
- Start Date: 2017-01-02 | ||
- RFC PR: (leave this empty) | ||
- Rust Issue: (leave this empty) | ||
|
||
# Summary | ||
[summary]: #summary | ||
|
||
Add a `'fn` lifetime that is bound to the scope of the body of the current innermost function or closure. | ||
|
||
# Motivation | ||
[motivation]: #motivation | ||
|
||
Doing this will enable us to declare that some values live longer than their containing scope, allowing us to "allocate" recursive data structures (e.g. linked lists) on the stack, as in: | ||
|
||
```rust | ||
struct Node<'a, T> { | ||
data: T, | ||
next: Option<&'a Node>, | ||
} | ||
|
||
fn foo() { | ||
// here we ascribe the `'fn` lifetime to `next` | ||
let mut head : Node<'fn, usize> = Node { data: 0, None }; | ||
for i in iter { | ||
head = Node { data: i, Some(&head) } | ||
} | ||
} | ||
``` | ||
|
||
# Detailed design | ||
[design]: #detailed-design | ||
|
||
There are three steps | ||
|
||
1. Bind `'fn` in every closure or function body, representing the lifetime of the body. | ||
This is equivalent to adding an additional `'fn` lifetime parameter to every function that cannot be used in the function's type (argument declaration or where clause), nor manually instantiated by a caller. | ||
Closure literals currently do not allow explicit parameters, but the desugaring would be the same if they did. | ||
Note that the `'fn` lifetimes will shadow each other, whereas we don't currently allow lifetime shadowing. | ||
|
||
2. Allow items inside functions to use lifetimes, including `'fn`, bound by the enclosing functions. | ||
Unclear how this interfacts with current well-formedness rules. | ||
|
||
3. Change axioms of borrowing so it is fine if the lifetime is already entered, as long as the borrow does not outlive it *from* the point of borrowing *after*. For example: | ||
```rust | ||
fn foo<'a>() { | ||
/* do stuff */ | ||
let a = 0; | ||
let b: &'a i32 = &a; | ||
} | ||
``` | ||
This is currently prohibited because the function body is entered before a is initialized (and thus able to be borrowed). | ||
Under the new rules, this would be allowed because `a` is uninitialized when the function happens, and the "past" before the borrow begins can be ignored. | ||
|
||
# Drawbacks | ||
[drawbacks]: #drawbacks | ||
|
||
This change makes a lifetime annotation change the behavior of the program (although in the motivating case, it only changes the program from "fails to compile" to "works"). | ||
The change incurs a considerable implementation cost, although a more complete "lifetime ascription" feature has been discussed in various places already (see the [Alternatives](alternatives) section for further discussion. | ||
|
||
# Alternatives | ||
[alternatives]: #alternatives | ||
|
||
- do nothing. Rust has been working quite well without this feature so far. | ||
It is only of use in very specific situations (on the other hand those situations are currently not well handled in Rust at all). | ||
|
||
- lifetime ascription – this is the idea to allow labels on each block (instead of only on loops) and treat the label names as lifetime designators. | ||
This is an awesome teaching device, as it allows us to make lifetimes explicit *in working code*, where we can now only use comments and pseudocode. | ||
Even with lifetime ascription, this feature is still useful, because ascribing a lifetime to a `fn` block gets awfully hard to parse, especially for a human. | ||
Consider following example: | ||
|
||
```Rust | ||
fn somewhat_awkward<'a, T>(Foo<'a>) -> Box<T> + 'a 'wha: { .. } | ||
``` | ||
|
||
# Unresolved questions | ||
[unresolved]: #unresolved-questions | ||
|
||
- Should we allow `break 'fn ..` with the same semantics as `return ..`. | ||
In [RFC 1624](https://github.com/rust-lang/rfcs/blob/master/text/1624-loop-break-value.md) we got breaking with a value out of loops, so the value part is already established. | ||
This provides one canonnical syntax for breaking out of a loops or functions, leaving `return` just a convenience. | ||
In a future with full lifetime ascription, this would further generalize to supporting all labeled blocks, not just loops and functions. | ||
|
||
A downside is `continue 'a` will still only make sense when `'a` is bound to a loop, and `break 'a` won't make sense when `'a` is a lifetime parameter. | ||
That means users will need to understand their are three tiers of lifetimes: loop, block (including `'fn`), and, parameter, where each is less usable than the last. | ||
Today loop lables and lifetimes are disjoint in that neither can be used where the other is expected, though they do share the same syntax. | ||
|
||
- does this allow for any unsound patterns? |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't really understand this sentence very well, but I think no change is needed here. A borrow starts from the point of borrow and is assigned some enclosing lifetime; the borrow lasts until the end of that lifetime. The two requirements are:
An example that shows this in action:
But maybe I am misunderstanding you, because your example seems pretty sketchy and I would not what that to be legal. You are taking a value with a limited lifetime (corresponding to the scope of the variable
a
, so let's call itscope(a)
) and creating a reference to it with a longer lifetime'a
. This seems clearly unsound. For example, a slight variation on your example would allow us to return a reference to the stack:Am I missing something here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nikomatsakis you are correct. I had noticed the mistake but hadn't gotten around to fixing it---sorry to have wasted your time with that.