From 9202650d4d390dfd60323d8689a12ae3527f6a0c Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 22 Jan 2025 11:53:59 +0000 Subject: [PATCH 1/4] Improve explanation of lifetimes 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/lifetimes/lifetime-annotations.md | 17 ++++++++--------- src/lifetimes/lifetime-elision.md | 13 +++++++++---- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/lifetimes/lifetime-annotations.md b/src/lifetimes/lifetime-annotations.md index d1df6e7957e2..c3024ea55ccc 100644 --- a/src/lifetimes/lifetime-annotations.md +++ b/src/lifetimes/lifetime-annotations.md @@ -12,12 +12,9 @@ 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. - -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. @@ -56,9 +53,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 From 1fd48d64c844a564103f32678449a2367865f5ca Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 22 Jan 2025 12:02:02 +0000 Subject: [PATCH 2/4] Update slide title to match SUMMARY.md --- src/SUMMARY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 3f620c18afa7..ed6a92a1fcc1 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) From 96c8e13def6573d7933008120e800521e85c36dc Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 23 Jan 2025 15:21:54 +0000 Subject: [PATCH 3/4] Ownership controls lifetimes --- src/lifetimes/lifetime-annotations.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/lifetimes/lifetime-annotations.md b/src/lifetimes/lifetime-annotations.md index c3024ea55ccc..e352f19d4f9d 100644 --- a/src/lifetimes/lifetime-annotations.md +++ b/src/lifetimes/lifetime-annotations.md @@ -12,9 +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`". +Only ownership, not lifetime annotations, control when objects are destroyed and +determine the concrete lifetime of a given value. The borrow checker just +ensures this is done safely. + 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. +callsites and within the function body, helping the borrow checker to do its +job. From 83cef91d4cec3c93f2b550b6ba3ddf01eadf266e Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 4 Feb 2025 20:05:13 +0000 Subject: [PATCH 4/4] address review comment --- src/lifetimes/lifetime-annotations.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lifetimes/lifetime-annotations.md b/src/lifetimes/lifetime-annotations.md index e352f19d4f9d..2b9a0ffda323 100644 --- a/src/lifetimes/lifetime-annotations.md +++ b/src/lifetimes/lifetime-annotations.md @@ -12,9 +12,9 @@ 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`". -Only ownership, not lifetime annotations, control when objects are destroyed and +Only ownership, not lifetime annotations, control when values are destroyed and determine the concrete lifetime of a given value. The borrow checker just -ensures this is done safely. +validates that borrows never extend beyond the concrete lifetime of the value. Explicit lifetime annotations, like types, are required on function signatures (but can be elided in common cases). These provide information for inference at