Skip to content
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

Improve explanation of lifetimes #2584

Merged
merged 4 commits into from
Feb 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@
- [Lifetimes](lifetimes.md)
- [Lifetime Annotations](lifetimes/lifetime-annotations.md)
- [Lifetime Elision](lifetimes/lifetime-elision.md)
- [Struct Lifetimes](lifetimes/struct-lifetimes.md)
- [Lifetimes in Data Structures](lifetimes/struct-lifetimes.md)
- [Exercise: Protobuf Parsing](lifetimes/exercise.md)
- [Solution](lifetimes/solution.md)

Expand Down
20 changes: 12 additions & 8 deletions src/lifetimes/lifetime-annotations.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ also be explicit: `&'a Point`, `&'document str`. Lifetimes start with `'` and
`'a` is a typical default name. Read `&'a Point` as "a borrowed `Point` which is
valid for at least the lifetime `a`".

Lifetimes are always inferred by the compiler: you cannot assign a lifetime
yourself. Explicit lifetime annotations create constraints where there is
ambiguity; the compiler verifies that there is a valid solution.
Only ownership, not lifetime annotations, control when values are destroyed and
determine the concrete lifetime of a given value. The borrow checker just
validates that borrows never extend beyond the concrete lifetime of the value.

Lifetimes become more complicated when considering passing values to and
returning values from functions.
Explicit lifetime annotations, like types, are required on function signatures
(but can be elided in common cases). These provide information for inference at
callsites and within the function body, helping the borrow checker to do its
job.

<!-- The multi-line formatting by rustfmt in left_most is apparently
intentional: https://github.com/rust-lang/rustfmt/issues/1908 -->
Expand Down Expand Up @@ -56,9 +58,11 @@ Add `'a` appropriately to `left_most`:
fn left_most<'a>(p1: &'a Point, p2: &'a Point) -> &'a Point {
```

This says, "given p1 and p2 which both outlive `'a`, the return value lives for
at least `'a`.
This says there is some lifetime `'a` which both `p1` and `p2` outlive, and
which outlives the return value. The borrow checker verifies this within the
function body, and uses this information in `main` to determine a lifetime for
`p3`.

In common cases, lifetimes can be elided, as described on the next slide.
Try dropping `p2` in `main` before printing `p3`.

</details>
13 changes: 9 additions & 4 deletions src/lifetimes/lifetime-elision.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ fn cab_distance(p1: &Point, p2: &Point) -> i32 {
(p1.0 - p2.0).abs() + (p1.1 - p2.1).abs()
}

fn nearest<'a>(points: &'a [Point], query: &Point) -> Option<&'a Point> {
fn find_nearest<'a>(points: &'a [Point], query: &Point) -> Option<&'a Point> {
let mut nearest = None;
for p in points {
if let Some((_, nearest_dist)) = nearest {
Expand All @@ -40,7 +40,11 @@ fn nearest<'a>(points: &'a [Point], query: &Point) -> Option<&'a Point> {

fn main() {
let points = &[Point(1, 0), Point(1, 0), Point(-1, 0), Point(0, -1)];
println!("{:?}", nearest(points, &Point(0, 2)));
let nearest = {
let query = Point(0, 2);
find_nearest(points, &Point(0, 2))
};
println!("{:?}", nearest);
}
```

Expand All @@ -49,12 +53,13 @@ fn main() {
In this example, `cab_distance` is trivially elided.

The `nearest` function provides another example of a function with multiple
references in its arguments that requires explicit annotation.
references in its arguments that requires explicit annotation. In `main`, the
return value is allowed to outlive the query.

Try adjusting the signature to "lie" about the lifetimes returned:

```rust,ignore
fn nearest<'a, 'q>(points: &'a [Point], query: &'q Point) -> Option<&'q Point> {
fn find_nearest<'a, 'q>(points: &'a [Point], query: &'q Point) -> Option<&'q Point> {
```

This won't compile, demonstrating that the annotations are checked for validity
Expand Down