-
Notifications
You must be signed in to change notification settings - Fork 312
Overhaul Lifetimes Section (was "Help Needed: Extend lifetimes section") #4
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
Changes from 9 commits
4bf5fa6
4b794ca
0a6ad61
005053e
87b4ca4
c5c93f2
7b93ec8
37fe162
09274f3
f6b3f2a
48f7e91
c1833cc
e4c2e8b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -213,3 +213,204 @@ totally ok*, because it keeps us from spending all day explaining our program | |
| to the compiler. However it does mean that several programs that are totally | ||
| correct with respect to Rust's *true* semantics are rejected because lifetimes | ||
| are too dumb. | ||
|
|
||
| # Inter-procedural Borrow Checking of Function Arguments | ||
|
|
||
| Consider the following program: | ||
|
|
||
| ```rust,ignore | ||
| fn main() { | ||
| let s1 = String::from("short"); | ||
| let s2 = String::from("a long long long string"); | ||
| print_shortest(&s1, &s2); | ||
| } | ||
|
|
||
| fn shortest<'k>(x: &'k str, y: &'k str) { | ||
| if x.len() < y.len() { | ||
| println!("{}", x); | ||
| } else { | ||
| println!("{}", y); | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| `print_shortest` simply prints the shorter of its two pass-by-reference | ||
|
||
| string arguments. In Rust, each let binding has its own scope. Let's make the | ||
| scopes introduced to `main` explicit: | ||
|
|
||
| ```rust,ignore | ||
| fn main() { | ||
| 's1 { | ||
| let s1 = String::from("short"); | ||
| 's2 { | ||
| let s2 = String::from("a long long long string"); | ||
| print_shortest(&s1, &s2); | ||
| } | ||
| } | ||
|
||
| ``` | ||
|
|
||
| And now let's explicitly mark the lifetimes of each reference's referent too: | ||
|
|
||
| ```rust,ignore | ||
| fn main() { | ||
| 's1 { | ||
| let s1 = String::from("short"); | ||
| 's2 { | ||
| let s2 = String::from("a long long long string"); | ||
| print_shortest(&'s1 s1, &'s2 s2); | ||
| } | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing |
||
| ``` | ||
|
|
||
| Now we see that the references passed as arguments to `print_shortest` | ||
| actually have different lifetimes (and thus a different type!) since the values | ||
| they refer to were introduced in different scopes. At the call site of | ||
| `print_shortest` the compiler must now check that the lifetimes in the | ||
| *caller* (`main`) are consistent with the lifetimes in the signature of the | ||
| *callee* (`print_shortest`). | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not how the lifetime system works. It's a constraint solver; they never have "wrong" lifetimes, and the lifetimes aren't checked if they're "right". It just tries to solve the system of constraints and either reports success or failure. |
||
|
|
||
| The signature of `print_shortest` simply requires that both of it's arguments | ||
|
||
| have the same lifetime (because both arguments are marked with the same | ||
| lifetime identifier in the signature). If in `main` we had done: | ||
|
|
||
| ```rust,ignore | ||
| print_shortest(&s1, &s1); | ||
|
|
||
|
||
| ``` | ||
|
|
||
| Then consistency is trivially proven, since both arguments would have | ||
| the same lifetime `&'s1` at the call-site. However, for our example, the | ||
| arguments have different lifetimes. We don't want Rust to reject the program | ||
| because it actually is safe. Instead the compiler uses some rules for | ||
| converting between lifetimes whilst retaining referential safety. The first | ||
| such rule is as follows: | ||
|
|
||
| > A function argument of type `&'p T` can be coerced with an argument of type | ||
| > `&'q T` if the lifetime of `&'p T` is equal or longer than `&'q T`. | ||
|
|
||
| At our call site, the type of the arguments are `&'s1 str` and `&'s2 str`, and | ||
| we know that a `&'s1 str'` outlives an `&'s2 str`, so we can substitute `&'s1 | ||
| s1` with `&'s2 s2`. After this both arguments are of lifetime `&'s2` and the | ||
| call-site is consistent with the signature of `print_shortest`. | ||
|
|
||
| [More formally, the basis for the above rule is in *type variance*. Under this | ||
|
||
| model, you would consider a longer lifetime a sub-type of a shorter lifetime, | ||
| and for function arguments to be *co-variant*. However, an understanding of | ||
| variance isn't strictly required to understand the Rust borrow checker. We've | ||
| tried here to instead to explain using intuitive terminlolgy.] | ||
|
|
||
| # Inter-procedural Borrow Checking of Function Return Values | ||
|
||
|
|
||
| Now consider a slight variation of this example: | ||
|
|
||
| ```rust,ignore | ||
| fn main() { | ||
| let s1 = String::from("short"); | ||
| let res; | ||
| let s2 = String::from("a long long long string"); | ||
| res = shortest(&s1, &s2); | ||
| println!("{}", res); | ||
| } | ||
|
|
||
| fn shortest<'k>(x: &'k str, y: &'k str) -> &'k str { | ||
| if x.len() < y.len() { | ||
| return x; | ||
| } else { | ||
| return y; | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| `print_shortest` has been renamed to `shortest`, which instead of printing, | ||
| now returns the shorter of the two strings. It does this using only references | ||
| for efficiency, avoiding the need to re-allocate a new string to pass back to | ||
| `main`. The responsibility of printing the result has been shifted to `main`. | ||
|
|
||
| Let's again de-sugar `main` by adding explicit scopes and lifetimes: | ||
|
|
||
| ```rust,ignore | ||
| fn main() { | ||
| 's1 { | ||
| let s1 = String::from("short"); | ||
| 'res { | ||
| let res: &'res str; | ||
| 's2 { | ||
| let s2 = String::from("a long long long string"); | ||
| res: &'res: str = shortest(&'s1 s1, &'s2 s2); | ||
| println!("{}", res); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| Again, at the call-site of `shortest` the comipiler needs to check the | ||
|
||
| consistency of the arguments in the caller with the signature of the callee. | ||
| The signature of `shortest` fisrt says that the two reference arguments have | ||
|
||
| the same lifetime, which can be prove ok in the same way as before, thus giving | ||
| us: | ||
|
|
||
| ```rust,ignore | ||
| res: &'res = shortest(&'s2 s1, &'s2 s2); | ||
| ``` | ||
|
|
||
| But we now have the additional reference to check. We must now prove that the | ||
| returned reference can have the same lifetime as the arguments of lifetime | ||
| `&'s2`. This brings us to a second rule: | ||
|
|
||
| > The return value of a function `&'r T` can be converted to an argument `&'s T` | ||
| > if the lifetime of `&'r T` is equal or shorter than `&'s T`. | ||
|
|
||
| To make our program compile, we would have to subsitute `res: &'res` for `res: | ||
| &'s2`, but we can't since `&'res` in fact out-lives `&'s2`. This program is | ||
| inconsistent and the compiler rightfully rejects the program because we | ||
| try make a reference (`res`) which outlives one of the values it may refer to | ||
| (`s2`). | ||
|
|
||
| [Formally, function return values are said to be *contravariant*, the opposite | ||
| of *covariant*.] | ||
|
|
||
| How can we fix this porgram? Well if you were to swap the `let s2 = ...` with | ||
| the `res = ...` line, you would have: | ||
|
|
||
| ```rust,ignore | ||
| fn main() { | ||
| let s1 = String::from("short"); | ||
| let s2 = String::from("a long long long string"); | ||
| let res; | ||
| res = shortest(&s1, &s2); | ||
| println!("{}", res); | ||
| } | ||
| ``` | ||
|
|
||
| Which de-sugars to: | ||
|
|
||
| ```rust,ignore | ||
| fn main() { | ||
| 's1 { | ||
| let s1 = String::from("short"); | ||
| 's2 { | ||
| let s2 = String::from("a long long long string"); | ||
| 'res { | ||
| let res: &'res str; | ||
| res: &'res str = shortest(&'s1 s1, &'s2 s2); | ||
| println!("{}", res); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| Then at the call-site of `shortest`: | ||
| * `&'s1 s1` outlives `&'s2 s2`, so we can replace the first argument with `&'s2 s1`. | ||
| * `&'res str` lives shorter than `'&s2`, so the return value lifetime can become `res: &'s2 str` | ||
|
|
||
| Leaving us with: | ||
|
|
||
| ```rust,ignore | ||
| res: &'s2 str = shortest(&'s2 s1, &'s2 s2); | ||
| ``` | ||
|
|
||
| Which matches the signature of `shortest` and thus this compiles. | ||
| Intuitively, the return reference can't point to a freed value as the values | ||
| live strictly longer than the return reference. | ||
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.
Is there any reason why this is
ignore? Seems like it should be compilable and runable, no?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.
Same with some of the other examples here