Borrowing, References and Slices
Moving a value into every function that needs to look at it would be exhausting: you would have to hand it back each time. Real libraries lend books out and get them back. Rust lets code do the same with a reference, and one rule keeps the lending safe.
The rule
Many readers or one writer, never both, and never outliving the owner.
A reference &value borrows without taking ownership. When the reference is no longer used, the borrow ends and the owner still has the value.
fn title_length(title: &str) -> usize {
title.len()
}
let title = String::from("Iron Tides");
let n = title_length(&title); // lend it; &String turns into &str automatically
println!("{title} {n}"); // still oursA mutable reference &mut value lets the borrower change the value. The owner must be mut too:
fn add_edition(title: &mut String) {
title.push_str(" (2nd ed.)");
}
let mut title = String::from("Iron Tides");
add_edition(&mut title);The borrowing rules, checked by the compiler:
- At any point you may have any number of
&references, or exactly one&mut, not both. - A reference must never outlive the value it points to.
A borrow lasts until its last use, not to the end of the block. That is why this fails:
let mut shelf = vec![String::from("Iron Tides")];
let first = &shelf[0]; // shared borrow starts
shelf.push(String::from("Night Garden")); // needs &mut: error[E0502]
println!("{first}"); // shared borrow still in useThe rule is not fussiness: push may move the vector's items to a bigger heap block, and first would point at freed memory. Move the println! above the push and it compiles.
Slices are references to part of a collection. &str is a slice of text; &[T] is a slice of an array or Vec:
let week = [4, 2, 5, 3, 6, 9, 1];
let weekend: &[u32] = &week[5..7]; // [9, 1]
let first_word: &str = &title[0..4]; // "Iron"String slice ranges are byte positions; cutting through the middle of a multi-byte character panics.
Prefer &str over &String and &[T] over &Vec<T> in parameters: they accept more kinds of input.
The shortcut
For each borrow, draw its lifespan from creation to last use. If a shared and a mutable lifespan overlap, or two mutable ones do, it will not compile.
Shared borrow
firstis created on line 2 and last used on line 4. Its lifespan is lines 2 to 4.
Drills
Answer fast — trust the rule.
How many &mut references to one value can be in use at the same moment?
Does let a = &title; let b = &title; println!("{a} {b}"); compile?
What is &week[1..3] for let week = [4, 2, 5, 3];?
Why is a function parameter of type &str usually better than String for a function that only reads?
In the shelf example, why does Rust refuse the push while first is still in use?
Remember
- Many
&readers or one&mutwriter, and no reference may outlive its owner. - A borrow lasts until its last use: draw each lifespan and look for overlap.
- Slices (
&str,&[T]) borrow part of a collection; prefer them in parameters.
