From 9c47da43464dd9c3aa4f14f16d684c00986936ff Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 5 Feb 2025 10:58:22 -0500 Subject: [PATCH] Improve explanation of lifetimes (#2584) This approach seems to balance formalism with understanding. That is, it doesn't mention contravariance, but suggests that lifetime annotations in parameters and return values mean "opposite" things. It also leverages the understanding that types must be specified in function signatures, and are used to check types in the function body and at call sites. --- src/SUMMARY.md | 2 +- src/lifetimes/lifetime-annotations.md | 20 ++++++++++++-------- src/lifetimes/lifetime-elision.md | 13 +++++++++---- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 2d803216caad..5e0aeae1fd66 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -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) diff --git a/src/lifetimes/lifetime-annotations.md b/src/lifetimes/lifetime-annotations.md index d1df6e7957e2..2b9a0ffda323 100644 --- a/src/lifetimes/lifetime-annotations.md +++ b/src/lifetimes/lifetime-annotations.md @@ -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. @@ -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`. diff --git a/src/lifetimes/lifetime-elision.md b/src/lifetimes/lifetime-elision.md index fdfeaceeded2..c996813f70f4 100644 --- a/src/lifetimes/lifetime-elision.md +++ b/src/lifetimes/lifetime-elision.md @@ -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 { @@ -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); } ``` @@ -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