Skip to main content
eLearner.app
Module 3 · Lesson 2 of 26/14 in the course~15 min
Module lessons (2/2)

References and Borrowing

In Rust, passing ownership back and forth to functions can be tedious. To solve this, Rust uses references (&).

Creating a reference to a value is called Borrowing.

Immutable References

A reference is declared by prefixing the type or variable with the & symbol. By default, references are immutable: they allow reading the value but not modifying it.

Code
fn main() {
    let s1 = String::from("hello");

    // We pass a reference to s1, not ownership
    let len = calculate_length(&s1);

    // s1 is still usable here!
    println!("The length of '{}' is {}.", s1, len);
}

fn calculate_length(s: &String) -> usize { // s is a reference to a String
    s.len()
} // Here, s goes out of scope, but since it doesn't own the value, nothing happens

Mutable References

If you need to modify a borrowed value, you must use a mutable reference with &mut. The original variable must also be declared mutable with mut:

Code
fn main() {
    let mut s = String::from("hello");

    // We pass a mutable reference
    change(&mut s);

    println!("{}", s); // Prints "hello, world"
}

fn change(some_string: &mut String) {
    some_string.push_str(", world");
}

The Rules of Borrowing

To prevent memory corruption and data races at compile-time, Rust enforces two fundamental rules:

  1. You can have any number of immutable references (&T) to a value at the same time.
  2. OR you can have exactly one mutable reference (&mut T) to a value at a time.

You absolutely cannot mix immutable and mutable references for the same value in the same scope:

Code
let mut s = String::from("hello");

let r1 = &s; // Valid
let r2 = &s; // Valid
// let r3 = &mut s; // COMPILATION ERROR! You cannot create &mut s if s is already borrowed as immutable

Reference Scope and Non-Lexical Lifetimes (NLL)

Historically, the scope of a reference lasted until the end of the block in which it was created. Today, Rust's compiler is smarter thanks to Non-Lexical Lifetimes (NLL): a reference's scope ends at the last line it is actually used, not necessarily at the end of the block.

This makes the following code perfectly valid:

Code
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
println!("{} and {}", r1, r2); // Last use of r1 and r2. The immutable references expire here!

let r3 = &mut s; // Valid! No active immutable references exist at this point

Try it yourself

Exercise#rust.m3.l2.e1
Attempts: 0Loading…

Pass an immutable reference of s1 to the calculate_length function using the & symbol.

Loading editor…
Show hint

Replace `/* TODO \_/`with`&s1` to pass an immutable reference to the string.

Solution available after 3 attempts

Exercise#rust.m3.l2.e2
Attempts: 0Loading…

Make the variable s mutable (let mut s) and pass a mutable reference (&mut s) to the change function to allow it to modify the string.

Loading editor…
Show hint

Use `let mut s`instead of`let s`and call`change(&mut s);`.

Solution available after 3 attempts

Exercise#rust.m3.l2.e3
Attempts: 0Loading…

Declare a variable s containing String::from("Rust"). Create two distinct immutable references r1 and r2 to s, and finally print both references separated by a space using the println! macro.

Loading editor…
Show hint

Assign `let r1 = &s;` and `let r2 = &s;` to obtain two immutable references, then print them with `println!("{} {}", r1, r2);`.

Solution available after 3 attempts