References and Lifetimes

Consider this Rust code:

#![allow(dead_code, unused_variables)]

#[derive(Debug, PartialEq)]
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let p1 = Point { x: 12, y: 10 };
    let p2 = &p1; // p2 takes a reference to the value of p1

    assert_eq!(Point { x: 12, y: 10 }, p1);  // ok
    assert_eq!(Point { x: 12, y: 10 }, p2);  // fails
    assert_eq!(Point { x: 12, y: 10 }, *p2); // ok, we use the dereference operator (*) to get the value
}

In Rust, references allow you to refer to some value without taking ownership of it. References are indicated by the use of an ampersand (&). In order to get to the value that a reference points to, we use the dereference operator (*).

Consider the following Rectangle struct and its implementation block:

#[derive(Debug)]
struct Rectangle {
    length: i32,
    width: i32,
}

impl Rectangle {
    pub fn new(length: i32, width: i32) -> Self {
        Self { length, width }
    }

    pub fn length(&self) -> i32 {
        self.length
    }

    pub fn width(&self)  -> i32 {
        self.width
    }
}

Here's a function to calculate the area of a rectangle. Notice that we are passing in the Rectangle by value:

fn calculate_area(rect: Rectangle) -> i32 {
    rect.length() * rect.width()
}

When we exercise the function as shown below, we get an error. Because we are passing by value, ownership of rect is moved into the calculate_area() function:

fn main() {
    let rect = Rectangle::new(6, 4);
    
    
    let area_of_rect = calculate_area(rect); // value moved into function
    
    
    println!("{}", area_of_rect); // prints: 24
    println!("{:?}", rect);       // won't work, rect no longer available here
}

One (inefficient) solution would be to make Rectangle cloneable:

#[derive(Debug, Clone)]
struct Rectangle {
    length: i32,
    width: i32,
}

fn main() {
    let rect = Rectangle::new(6, 4);
    
    let area_of_rect = calculate_area(rect.clone());

    println!("{}", area_of_rect); // prints: 24
    println!("{:?}", rect);       // works fine, prints: Rectangle { length: 6, width: 4 }
}

When we are dealing with a lot of objects, then cloning wouldn't be a great solution.

A better approach would be to make the calculate_area() function take a reference to the Rectangle (i.e. &Rectangle) as input. In other words, pass the value by reference.

fn calculate_area(rect: &Rectangle) -> i32 {
    rect.length() * rect.width()
}

And now we can use the function as follows:

#[derive(Debug)] // no more Clone
struct Rectangle {
    length: i32,
    width: i32,
}

fn main() {
    let rect = Rectangle::new(6, 4);
    
    
    let area_of_rect = calculate_area(&rect); // takes a reference as input
    
    
    println!("{}", area_of_rect); // prints: 24
    println!("{:?}", rect);       // works fine, prints: Rectangle { length: 6, width: 4 }
}

Finally, let's consider the following case that doesn't compile:

fn main() {
    let rect1;
    
    {
        let rect2 = Rectangle::new(6, 4);
        rect1 = &rect2;
    } // rect2 goes out of scope/is dropped here
    
    // rect1, which holds a reference to rect2 is invalid here
    println!("{:?}", rect1); // won't work: borrowed value (&rect2) does not live long enough
}

Here's the same code with lifetime annotations:

fn main() {
    let rect1;                            // -----------+--- 'a
                                          //            |
    {                                     //            |  
        let rect2 = Rectangle::new(6, 4); // --+-- 'b   |
        rect1 = &rect2;                   //   |        |
    }                                     // --+        |
                                          //            |
    println!("{:?}", rect1);              //            |
}                                         // -----------+

As you can see, the lifetime of rect2 ('b) is shorter than that of rect1 ('a). That's what the error message indicates: borrowed value (&rect2) does not live long enough. Therefore trying to access rect2 after it's been dropped results in a compile-time error.

The lifetime annotation 'a is pronounced "tick A".

The Rust compiler has a borrow checker that keeps track of variable lifetimes to determine whether all borrows are valid.

The version below works fine:

fn main() {
    let rect2 = Rectangle::new(6, 4); // -----------+--- 'b
                                      //            |
    let rect1 = &rect2;               // --+-- 'a   |  
                                      //   |        |
    println!("{:?}", rect1);          //   |        |
                                      //---+        |
}                                     // -----------+

See also: