Borrowing and lifetimes without fear

Many readers or one writer. The single rule behind references, and what a lifetime really says.

Borrowing and lifetimes without fear

In part one, every value had one owner, and passing a value to a function gave it away. That is safe and awkward. Borrowing fixes the awkward part. You lend a value, the borrower uses it, and ownership never changes hands.

A reference is a loan. The owner keeps the value, and the loan has an end.
A reference is a loan. The owner keeps the value, and the loan has an end.

References

A reference is written with &. It lets a function read a value without owning it.

// file: src/main.rs
fn length(s: &String) -> usize {
    s.len()
} // s goes out of scope, and nothing is dropped

fn main() {
    let name = String::from("ferris");
    let n = length(&name);
    println!("{} has {} letters", name, n); // name is still ours
}

The function borrowed name for the length of the call. When it returned, the loan ended. The owner never changed.

The one rule

Borrowing has a single rule with two halves. At any moment, a value can have:

  • any number of shared references (&T), which can only read, or
  • exactly one exclusive reference (&mut T), which can read and write.

You can have many readers or one writer, never both. This is the rule that makes data races impossible, and it is checked when the program is compiled.

let mut scores = vec![10, 20, 30];

let first = &scores[0];      // shared borrow starts
scores.push(40);             // error: needs an exclusive borrow
println!("{}", first);       // shared borrow is still in use here

The error is correct, and it prevents a real bug. push may move the vector to a bigger block of memory. After that, first would point to freed memory. In C++, this compiles and fails at some later time. In Rust, it does not compile.

The borrow checker is not being difficult. It has found a bug you have not had yet.

Lena Fischer

Mutable borrows

To let a function change a value, lend it exclusively.

fn add_bonus(scores: &mut Vec<i32>) {
    for s in scores.iter_mut() {
        *s += 5;
    }
}

let mut scores = vec![10, 20, 30];
add_bonus(&mut scores);

While add_bonus holds the exclusive reference, nothing else can touch scores. When the call ends, you have it back.

Borrows end when they are last used

A common surprise, and a pleasant one: a borrow lasts until its last use, not until the closing brace.

let mut text = String::from("hi");
let r = &text;
println!("{}", r);   // last use of r: the shared borrow ends here
text.push_str(" there"); // fine

If a borrow seems to block you, check whether you still use the reference further down. Moving one line is often the whole fix.

Lifetimes, without the fear

A lifetime is the span of code in which a reference is valid. You already reasoned about them above. Most of the time, the compiler works them out and you write nothing.

You write a lifetime only when a function returns a reference and the compiler cannot tell which input it came from.

fn longest<'a>(a: &'a str, b: &'a str) -> &'a str {
    if a.len() >= b.len() { a } else { b }
}

Read 'a as a name for "some span of code". The signature says: the result is valid for as long as both inputs are valid. It does not change how long anything lives. It describes a relationship so that the compiler can check the callers.

💡
A practical rule. If a function takes one reference and returns one reference, you never need to write a lifetime. If you find yourself adding lifetimes to a struct, ask whether the struct should own its data. Often, it should.

Dangling references cannot exist

This function tries to return a reference to a local value.

fn make() -> &String {
    let s = String::from("temp");
    &s
} // s is dropped here, so the reference would point at nothing

The compiler rejects it. The fix is to return the value, not a reference to it. Ownership moves to the caller, and nothing dangles.

Slices are borrows too

A string slice &str and an array slice &[T] are references to part of a value. They follow the same rule.

let line = String::from("GET /index.html");
let method = &line[0..3]; // borrows part of line
println!("{}", method);

Prefer &str to &String in function arguments. It accepts both string literals and owned strings, and it costs nothing.

Patterns that calm the borrow checker

ProblemUsual fix
Borrowed value used after a changeFinish using the reference first, then change
Two mutable borrows of one structBorrow the fields separately, or split into methods
Reference outlives its ownerReturn an owned value
Long-lived shared dataRc<T> or Arc<T>

Is clone a sign of bad code?

No. Cloning a small value to keep code simple is a reasonable trade. Measure before you remove clones for speed.

What does 'static mean?

The reference is valid for the whole run of the program. String literals are 'static because they live in the binary.

What comes next

With ownership and borrowing, you can write most programs. The third piece is how Rust deals with things that fail. It has no exceptions, and the replacement is one of the best parts of the language. Part three covers Result, the ? operator and how to design your own error types.

Great! Check your inbox and click the link to confirm.