Lifetimes in Function Calls

Lifetimes for function arguments and return values must be fully specified, but Rust allows lifetimes to be elided in most cases with a few simple rules. This is not inference — it is just a syntactic shorthand.

  • Each argument which does not have a lifetime annotation is given one.
  • If there is only one argument lifetime, it is given to all un-annotated return values.
  • If there are multiple argument lifetimes, but the first one is for self, that lifetime is given to all un-annotated return values.
  1. #[derive(Debug)]
  2. struct Point(i32, i32);
  3. fn cab_distance(p1: &Point, p2: &Point) -> i32 {
  4.     (p1.0 - p2.0).abs() + (p1.1 - p2.1).abs()
  5. }
  6. fn nearest<'a>(points: &'a [Point], query: &Point) -> Option<&'a Point> {
  7.     let mut nearest = None;
  8.     for p in points {
  9.         if let Some((_, nearest_dist)) = nearest {
  10.             let dist = cab_distance(p, query);
  11.             if dist < nearest_dist {
  12.                 nearest = Some((p, dist));
  13.             }
  14.         } else {
  15.             nearest = Some((p, cab_distance(p, query)));
  16.         };
  17.     }
  18.     nearest.map(|(p, _)| p)
  19. }
  20. fn main() {
  21.     let points = &[Point(1, 0), Point(1, 0), Point(-1, 0), Point(0, -1)];
  22.     println!("{:?}", nearest(points, &Point(0, 2)));
  23. }

This slide should take about 5 minutes.

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.

Try adjusting the signature to “lie” about the lifetimes returned:

  1. fn nearest<'a, 'q>(points: &'a [Point], query: &'q Point) -> Option<&'q Point> {

This won’t compile, demonstrating that the annotations are checked for validity by the compiler. Note that this is not the case for raw pointers (unsafe), and this is a common source of errors with unsafe Rust.

Students may ask when to use lifetimes. Rust borrows always have lifetimes. Most of the time, elision and type inference mean these don’t need to be written out. In more complicated cases, lifetime annotations can help resolve ambiguity. Often, especially when prototyping, it’s easier to just work with owned data by cloning values where necessary.