Address comments and small improvements.

pull/4/head
Edd Barrett 8 years ago
parent c1833ccbf3
commit e4c2e8b8aa

@ -1,6 +1,6 @@
# Lifetimes # Lifetimes
Rust enforces these rules through *lifetimes*. Lifetimes are effectively Rust ensures memory safety through *lifetimes*. Lifetimes are effectively
just names for scopes somewhere in the program. Each reference, just names for scopes somewhere in the program. Each reference,
and anything that contains a reference, is tagged with a lifetime specifying and anything that contains a reference, is tagged with a lifetime specifying
the scope it's valid for. the scope it's valid for.
@ -14,7 +14,7 @@ make your code Just Work.
However once you cross the function boundary, you need to start talking about However once you cross the function boundary, you need to start talking about
lifetimes. Lifetimes are denoted with an apostrophe: `'a`, `'static`. To dip lifetimes. Lifetimes are denoted with an apostrophe: `'a`, `'static`. To dip
our toes with lifetimes, we're going to pretend that we're actually allowed our toes into lifetimes, we're going to pretend that we're actually allowed
to label scopes with lifetimes, and desugar the examples from the start of to label scopes with lifetimes, and desugar the examples from the start of
this chapter. this chapter.
@ -81,7 +81,7 @@ z = y;
# Example: references that outlive referents # Example: References that Outlive Referents
Alright, let's look at some of those examples from before: Alright, let's look at some of those examples from before:
@ -165,7 +165,7 @@ our implementation *just a bit*.)
# Example: aliasing a mutable reference # Example: Aliasing a Mutable Reference
How about the other example: How about the other example:
@ -214,9 +214,10 @@ 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 correct with respect to Rust's *true* semantics are rejected because lifetimes
are too dumb. are too dumb.
# Inter-procedural Borrow Checking of Function Arguments # Inter-procedural Borrow Checking
Consider the following program: Earlier we discussed lifetime constraints within a single function. Now let's
talk about the constraints *between* functions. Consider the following program:
```rust ```rust
fn main() { fn main() {
@ -235,22 +236,8 @@ fn print_shortest<'k>(x: &'k str, y: &'k str) {
``` ```
`print_shortest` prints the shorter of its two pass-by-reference `print_shortest` prints the shorter of its two pass-by-reference
string arguments. In Rust, each let binding has its own scope. Let's make the string arguments. Let's first de-sugar to make the reference lifetimes of
scopes introduced to `main` explicit: `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 ```rust,ignore
fn main() { fn main() {
@ -264,6 +251,9 @@ fn main() {
} }
``` ```
(for brevity, we don't show the third implicit scope that would be introduced
to limit to lifetimes of the borrows in the call to `print_shortest`)
Now we see that the references passed as arguments to `print_shortest` Now we see that the references passed as arguments to `print_shortest`
actually have different lifetimes (and thus a different type!) since the values 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 they refer to were introduced in different scopes. At the call site of
@ -286,23 +276,18 @@ because it actually is safe. Instead the compiler uses some rules for
converting between lifetimes whilst retaining referential safety. The first converting between lifetimes whilst retaining referential safety. The first
such rule is as follows: such rule is as follows:
> A function argument of type `&'p T` can be coerced with an argument of type > A reference can always be shrunk to one of a shorter lifetime. In other
> `&'q T` if the lifetime of `&'p T` is equal or longer than `&'q T`. > words, `&'a T` can be implicitly converted to `&'b T` as long as `'a`
outlives `'b.`
At our call site, the type of the arguments are `&'s1 str` and `&'s2 str`, and 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 we know that a `&'s1 str` outlives an `&'s2 str`, so we can shrink `&'s1
s1` with `&'s2 s2`. After this both arguments are of lifetime `&'s2` and the s1` to `&'s2 s1`. After this both arguments are of lifetime `&'s2` and the
call-site is consistent with the signature of `print_shortest`. 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 ## Inter-procedural Borrow Checking of Function Return Values
Now consider a slight variation of this example: Now consider a slight variation of the previous example:
```rust,ignore ```rust,ignore
fn main() { fn main() {
@ -324,7 +309,7 @@ fn shortest<'k>(x: &'k str, y: &'k str) -> &'k str {
`print_shortest` has been renamed to `shortest`, which instead of printing, `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 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 for efficiency, avoiding the need to allocate a new string to pass back to
`main`. The responsibility of printing the result has been shifted to `main`. `main`. The responsibility of printing the result has been shifted to `main`.
Let's again de-sugar `main` by adding explicit scopes and lifetimes: Let's again de-sugar `main` by adding explicit scopes and lifetimes:
@ -345,73 +330,25 @@ fn main() {
} }
``` ```
Again, at the call-site of `shortest` the compiler needs to check the Again, at the call-site of `shortest` the compiler needs to check the consistency
consistency of the arguments in the caller with the signature of the callee. of the arguments in the caller with the signature of the callee. The signature
The signature of `shortest` first says that the two reference arguments have of shortest says that all three references must have the same lifetime `'k`, so
the same lifetime, which can be prove ok in the same way as before, thus giving we have to find a lifetime `'k` such that:
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` * `&s1`: `&'s1 str` can be converted to the first argument `&'k str`
> if the lifetime of `&'r T` is equal or shorter than `&'s T`. * `&s2`: `&'s2 str` can be converted to the second argument `&'k str`
* The return value `&'k str` can be converted to res: `&'res str`
To make our program compile, we would have to subsitute `res: &'res` for `res: This leads to three requirements:
&'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 * `'s1` must outlive `'k`
> of *covariant*. * `'s2` must outlive `'k`
* `'k` must outlive `'res`
How can we fix this porgram? Well if you were to swap the `let s2 = ...` with So by transitivity (`'s2` outlives `'k` outlives `'res`), we also require `'s2`
the `res = ...` line, you would have: to outlive `'res`, which is not the case. The borrow checker rightfully
rejects our program because we are making a reference (`res`) which outlives
```rust,ignore one of the values it may refer to (`s2`).
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. The program is fixed by swapping the definition order of `res` and `s2`. Then
Intuitively, the return reference can't point to a freed value as the values `res` lives longer than both `s1` and `s2`.
live strictly longer than the return reference.

Loading…
Cancel
Save