Skip to content
Closed
201 changes: 201 additions & 0 deletions src/lifetimes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor

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?

Copy link
Contributor

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

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"simply" should be taken out

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);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing }

```

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);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The 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`).
Copy link
Contributor

Choose a reason for hiding this comment

The 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kill all "simply"s

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);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: unnecessary newline here

```

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this would be all in >s, not [].

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be a smaller heading than #


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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: comipiler

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: fisrt

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.