You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
nomicon/src/lifetimes.md

314 lines
9.9 KiB

# Lifetimes
10 years ago
10 years ago
Rust enforces these rules through *lifetimes*. Lifetimes are effectively
just names for scopes somewhere in the program. Each reference,
10 years ago
and anything that contains a reference, is tagged with a lifetime specifying
the scope it's valid for.
10 years ago
10 years ago
Within a function body, Rust generally doesn't let you explicitly name the
10 years ago
lifetimes involved. This is because it's generally not really necessary
to talk about lifetimes in a local context; Rust has all the information and
can work out everything as optimally as possible. Many anonymous scopes and
temporaries that you would otherwise have to write are often introduced to
10 years ago
make your code Just Work.
10 years ago
10 years ago
However once you cross the function boundary, you need to start talking about
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
to label scopes with lifetimes, and desugar the examples from the start of
this chapter.
10 years ago
Originally, our examples made use of *aggressive* sugar -- high fructose corn
syrup even -- around scopes and lifetimes, because writing everything out
explicitly is *extremely noisy*. All Rust code relies on aggressive inference
and elision of "obvious" things.
10 years ago
10 years ago
One particularly interesting piece of sugar is that each `let` statement implicitly
introduces a scope. For the most part, this doesn't really matter. However it
does matter for variables that refer to each other. As a simple example, let's
completely desugar this simple piece of Rust code:
```rust
let x = 0;
let y = &x;
let z = &y;
```
The borrow checker always tries to minimize the extent of a lifetime, so it will
likely desugar to the following:
10 years ago
```rust,ignore
// NOTE: `'a: {` and `&'b x` is not valid syntax!
10 years ago
'a: {
let x: i32 = 0;
'b: {
10 years ago
// lifetime used is 'b because that's good enough.
let y: &'b i32 = &'b x;
10 years ago
'c: {
// ditto on 'c
let z: &'c &'b i32 = &'c y;
10 years ago
}
}
}
```
Wow. That's... awful. Let's all take a moment to thank Rust for making this easier.
10 years ago
Actually passing references to outer scopes will cause Rust to infer
a larger lifetime:
```rust
let x = 0;
let z;
let y = &x;
z = y;
```
```rust,ignore
'a: {
let x: i32 = 0;
'b: {
let z: &'b i32;
'c: {
// Must use 'b here because this reference is
// being passed to that scope.
let y: &'b i32 = &'b x;
z = y;
}
}
}
```
# Example: references that outlive referents
Alright, let's look at some of those examples from before:
10 years ago
```rust,ignore
fn as_str(data: &u32) -> &str {
let s = format!("{}", data);
&s
}
```
desugars to:
```rust,ignore
fn as_str<'a>(data: &'a u32) -> &'a str {
'b: {
let s = format!("{}", data);
return &'a s;
10 years ago
}
}
```
This signature of `as_str` takes a reference to a u32 with *some* lifetime, and
promises that it can produce a reference to a str that can live *just as long*.
Already we can see why this signature might be trouble. That basically implies
10 years ago
that we're going to find a str somewhere in the scope the reference
to the u32 originated in, or somewhere *even earlier*. That's a bit of a tall
order.
10 years ago
We then proceed to compute the string `s`, and return a reference to it. Since
the contract of our function says the reference must outlive `'a`, that's the
lifetime we infer for the reference. Unfortunately, `s` was defined in the
scope `'b`, so the only way this is sound is if `'b` contains `'a` -- which is
clearly false since `'a` must contain the function call itself. We have therefore
created a reference whose lifetime outlives its referent, which is *literally*
the first thing we said that references can't do. The compiler rightfully blows
up in our face.
10 years ago
To make this more clear, we can expand the example:
10 years ago
```rust,ignore
fn as_str<'a>(data: &'a u32) -> &'a str {
10 years ago
'b: {
let s = format!("{}", data);
return &'a s
10 years ago
}
}
10 years ago
fn main() {
'c: {
let x: u32 = 0;
'd: {
// An anonymous scope is introduced because the borrow does not
// need to last for the whole scope x is valid for. The return
10 years ago
// of as_str must find a str somewhere before this function
// call. Obviously not happening.
println!("{}", as_str::<'d>(&'d x));
}
10 years ago
}
}
```
Shoot!
10 years ago
Of course, the right way to write this function is as follows:
10 years ago
```rust
fn to_string(data: &u32) -> String {
format!("{}", data)
10 years ago
}
```
We must produce an owned value inside the function to return it! The only way
we could have returned an `&'a str` would have been if it was in a field of the
`&'a u32`, which is obviously not the case.
10 years ago
(Actually we could have also just returned a string literal, which as a global
can be considered to reside at the bottom of the stack; though this limits
our implementation *just a bit*.)
10 years ago
10 years ago
# Example: aliasing a mutable reference
How about the other example:
```rust,ignore
let mut data = vec![1, 2, 3];
let x = &data[0];
data.push(4);
println!("{}", x);
```
```rust,ignore
'a: {
let mut data: Vec<i32> = vec![1, 2, 3];
'b: {
// 'b is as big as we need this borrow to be
// (just need to get to `println!`)
let x: &'b i32 = Index::index::<'b>(&'b data, 0);
'c: {
// Temporary scope because we don't need the
// &mut to last any longer.
Vec::push(&'c mut data, 4);
}
println!("{}", x);
}
}
```
10 years ago
10 years ago
The problem here is a bit more subtle and interesting. We want Rust to
reject this program for the following reason: We have a live shared reference `x`
10 years ago
to a descendant of `data` when we try to take a mutable reference to `data`
10 years ago
to `push`. This would create an aliased mutable reference, which would
violate the *second* rule of references.
10 years ago
However this is *not at all* how Rust reasons that this program is bad. Rust
doesn't understand that `x` is a reference to a subpath of `data`. It doesn't
understand Vec at all. What it *does* see is that `x` has to live for `'b` to
be printed. The signature of `Index::index` subsequently demands that the
10 years ago
reference we take to `data` has to survive for `'b`. When we try to call `push`,
it then sees us try to make an `&'c mut data`. Rust knows that `'c` is contained
within `'b`, and rejects our program because the `&'b data` must still be live!
10 years ago
10 years ago
Here we see that the lifetime system is much more coarse than the reference
semantics we're actually interested in preserving. For the most part, *that's
totally ok*, because it keeps us from spending all day explaining our program
10 years ago
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.
# A More Involved Example.
Consider the following program:
```rust,ignore
fn main() {
let s1 = String::from("short");
let s2r = &String::from("a long long long string");
println!("{}", shortest(&s1, s2r));
}
fn shortest<'k>(x: &'k str, y: &'k str) -> &'k str {
if x.len() < y.len() {
return x;
} else {
return y;
}
}
```
8 years ago
The idea is that `shortest()` returns a reference to the shorter of the two
strings referenced by its arguments, but *without* allocating a new string.
Let's de-sugar `main()` so we can see the implicit lifetimes:
```rust,ignore
fn main() {
'a {
let s1 = String::from("short");
b' {
let s2r: &'b = &'b String::from("a long long long string");
'c {
// Annonymous scope for the borrow of s1
println!("{}", shortest(&'c s1, s2r));
}
}
}
}
```
Now we see that the references passed as arguments to `shortest()`, i.e. `&s1`
and `&s2`, actually have different lifetimes (`&'b` and `&'c` respectively), since the borrows occur in different scopes.
However, the signature of
`shortest()` requires that these two references (and also the returned
reference, which has lifetime `'c`) have the same lifetime. So how does the
compiler make sure this is the case?
At the call-site of `shortest()`, the compiler must try to *convert* the lifetimes of
the references marked `&'a` in the signature of `shortest()`
into a single compatible lifetime. This new lifetime must be shorter than, or equally as
long as, all three of the original reference lifetimes involved. In other words, we must convert to the shortest of the three lifetimes to `&'c`. Conversion from a reference `&'o` can be converted to to `&'p` if `'o` lives at least as long as `'p`, therefore:
* `The first argument &'c s1` already has lifetime `&'c`, so we don't have to do anything here.
* `&'b` outlives `&'c`, so we can convert `s2r: &'b` to `s2r: &'c`.
* The returned reference has lifetime `&'c` already.
After conversion, the call-site satisfies the signature of `shorter()`, we have
proven the lifetimes in this program to be consistent, and therefore the
compiler accepts the program.
Now consider a slight variation on `main()` like this:
```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);
}
```
De-sugared it looks like this:
```rust,ignore
fn main() {
'a {
let s1 = String::from("short");
'b {
let res: &'b str;
'c {
let s2 = String::from("a long long long string");
'd {
// Annonymous scope for the borrows of s1 and s2
// Assigning to the outer scope causes s1 and s2 to have 'b
res: &'b = shortest(&'d s1, &'d s2);
println!("{}", res);
}
}
}
}
}
```
XXX: Something is wrong. The above program does not compile, so we should be
able to show that the lifetimes are inconsistent. To do so we would to be have
to show that we can't convert `&'b` to `&'d`, but since `&'b` outlives `&'d`,
we can. Hrmm.