References

References must never outlive their referents.

References to Values

As long as there are shared references to a value, not even its owner can modify it; the value is locked down.

If there is a mutable reference to a value, it has exclusive access to the value; you can’t use the owner at all, until the mutable reference goes away.

Working with References

To create a mutable reference, use the &mut operator:

let mut y = 32;
let m = &mut y; // &mut y is a mutable reference to y

The . operator implicitly dereferences its left operand, if needed:

struct Anime { name: &'static str, bechdel_pass: bool };
let aria = Anime { name: "Aria: The Animation", bechdel_pass: true };
let anime_ref = &aria;
assert_eq!(anime_ref.name, "Aria: The Animation");
  
// Equivalent to the above, but with the dereference written out:
assert_eq!((*anime_ref).name, "Aria: The Animation");

let mut v = vec![1973, 1968];
v.sort(); // implicitly borrows a mutable reference to v
(&mut v).sort(); // equivalent, but more verbose

In Rust you use the & and * operators to create and follow references, with the exception of the . operator, which borrows and dereferences implicitly.

References to References

Rust permits references to references:

struct Point { x: i32, y: i32 }
let point = Point { x: 1000, y: 729 }; let r: &Point = &point;
let rr: &&Point = &r;
let rrr: &&&Point = &rr;

The . operator follows as many references as it takes to find its target.

Like the . operator, Rust’s comparison operators “see through” any number of references.

References Are Never Null

In Rust, if you need a value that is either a reference to something or not, use the type Option<&T>.

Borrowing References to Arbitrary Expressions

fn factorial(n: usize) -> usize { 
  (1..n+1).product()
}
let r = &factorial(6);
assert_eq!(r + &1009, 1729);

In situations like this, Rust simply creates an anonymous variable to hold the expression’s value and makes the reference point to that.

References to Slices and Trait Objects

Rust also includes two kinds of fat pointers, two-word values carrying the address of some value, along with some further information necessary to put the value to use.

  • A reference to a slice carries the starting address of the slice and its length.

  • A reference to a value that implements a certain trait. A trait object carries a value’s address and a pointer to the trait’s implementation appropriate to that value, for invoking the trait’s methods.

Reference Safety

Borrowing a Local Variable

Rust tries to assign each reference type in your program a lifetime that meets the constraints imposed by how it is used.

At run time, a reference is nothing but an address; its lifetime is part of its type and has no run-time representation.

A variable’s lifetime must contain or enclose that of the reference borrowed from it.

let v = vec![1, 2, 3];
let r = &v[1];

Since v owns the vector, which owns its elements, the lifetime of v must enclose that of the reference type of &v[1].

Receiving References as Function Arguments

Rust’s equivalent of a global variable is called a static: it’s a value that’s created when the program starts and lasts until it terminates.Consider a function which assigns value to a static variable:

static mut STASH: &i32 = &128;

// Bad Attempt
fn f(p: &i32) {
	unsafe {
		STASH = p;
	} 
}

// equals to `fn f<'a>(p: &'a i32)`
// the lifetime 'a (pronounced “tick A”) is a lifetime parameter of f. You can read <'a> as // “for any lifetime 'a”

// Good Attempt
fn f(p: &'static i32) {
    unsafe {
        STASH = p;
    }
}

Bad Attempt: Since we must allow 'a to be any lifetime, things had better work out if it’s the smallest possible lifetime: one just enclosing the call to f. Then there is a mismatch between p and the STASH regarding lifetime.

Passing References to Functions

fn f(p: &'static i32) { ... }

let x = 10;
f(&x);

This fails to compile: the reference &x must not outlive x, but by passing it to f, we constrain it to live at least as long as 'static.

Returning References

fn smallest(v: &[i32]) -> &i32 {
    let mut s = &v[0];
    for r in &v[1..] {
        if *r < *s { s = r; }
    }
    s
}

// The signature is equivalent to fn smallest<'a>(v: &'a [i32]) -> &'a i32 { ... }

Suppose we call smallest like this:

let s;
{
    let parabola = [9, 4, 1, 0, 1, 4, 9];
    s = smallest(&parabola);
}
assert_eq!(*s, 0); // bad: points to element of dropped array

The lifetime of &parabola and s should be the same as dictated by the funtion signature, which assigns the same lifetime parameter 'a.

Structs Containing References

Whenever a reference type appears inside another type’s definition, you must write out its lifetime.

struct S<'a> {
    r: &'a i32
}

struct D<'a> {
    s: S<'a>
}

// By taking a lifetime parameter 'a and using it in s’s type, we’ve allowed Rust to relate D value’s lifetime to that of the reference its S holds.

Suppose we have a parsing function that takes a slice of bytes and returns a structure holding the results of the parse:

fn parse_record<'i>(input: &'i [u8]) -> Record<'i> { ... }

Without looking into the definition of the Record type at all, we can tell that, if we receive a Record from parse_record, whatever references it contains must point into the input buffer we passed in, and nowhere else (except perhaps at 'static values).

Distinct Lifetime Parameters

struct S<'a, 'b> {
    x: &'a i32,
    y: &'b i32
}

Omitting Lifetime Parameters

fn sum_r_xy(r: &i32, s: S) -> i32 {
    r + s.x + s.y
}

// The function's signature is shorthand for:
// fn sum_r_xy<'a, 'b, 'c>(r: &'a i32, s: S<'b, 'c>) -> i32

If there’s only a single lifetime that appears among your function’s parameters, then Rust assumes any lifetimes in your return value must be that one:

fn first_third(point: &[i32; 3]) -> (&i32, &i32) {
    (&point[0], &point[2])
}

// fn first_third<'a>(point: &'a [i32; 3]) -> (&'a i32, &'a i32)

If your function is a method on some type and takes its self parameter by reference, then that breaks the tie: Rust assumes that self’s lifetime is the one to give everything in your return value.

struct StringTable {
    elements: Vec<String>,
}

impl StringTable {
    fn find_by_prefix(&self, prefix: &str) -> Option<&String> {
        for i in 0 .. self.elements.len() {
            if self.elements[i].starts_with(prefix) {
                return Some(&self.elements[i]);
            }
        }
        None
    }
}

// fn find_by_prefix<'a, 'b>(&'a self, prefix: &'b str) -> Option<&'a String>

Sharing Versus Mutation

We may borrow a mutable reference to the vector, and we may borrow a shared reference to its elements, but those two references’ lifetimes must not overlap.

  • Shared access is read-only access.

  • Mutable access is exclusive access.

let mut x = 10;
let r1 = &x;
let r2 = &x;     // ok: multiple shared borrows permitted
x += 10;         // error: cannot assign to `x` because it is borrowed
let m = &mut x;  // error: cannot borrow `x` as mutable because it is
                 // also borrowed as immutable
println!("{}, {}, {}", r1, r2, m); // the references are used here,
                                   // so their lifetimes must last
                                   // at least this long
let mut y = 20;
let m1 = &mut y;
let m2 = &mut y;  // error: cannot borrow as mutable more than once
let z = y;        // error: cannot use `y` because it was mutably borrowed
println!("{}, {}, {}", m1, m2, z); // references are used here
let mut w = (107, 109);
let r = &w;
let r0 = &r.0;         // ok: reborrowing shared as shared
let m1 = &mut r.1;     // error: can't reborrow shared as mutable
println!("{}", r0);    // r0 gets used here
let mut v = (136, 139);
let m = &mut v;
let m0 = &mut m.0;      // ok: reborrowing mutable from mutable
*m0 = 137;
let r1 = &m.1;          // ok: reborrowing shared from mutable,
                        // and doesn't overlap with m0
v.1;                    // error: access through other paths still forbidden
println!("{}", r1);     // r1 gets used here

Last updated