1
0
mirror of https://github.com/google/comprehensive-rust.git synced 2025-03-27 16:25:43 +02:00

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.
This commit is contained in:
Dustin J. Mitchell 2025-02-05 10:58:22 -05:00 committed by GitHub
parent 715a23e7a8
commit fd7cb04a62
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 22 additions and 13 deletions

View File

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

View File

@ -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 -->
@ -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>

View File

@ -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