As we saw in Part 6, if you pass a value like String to a function, the ownership moves entirely. It's inconvenient to get it back as a return value every time. Rust provides references to briefly “borrow” a value without transferring ownership. The borrowing rules you learn in this episode lead directly to safe concurrency.
Learning Objectives
- Use the shared reference
&Tand the variable reference&mut Tseparately. - Memorize the borrowing rules (1 variable vs. N immutable) and apply them in the code.
- Understand how dangling references are blocked at compile time.
- Receives a reference to the function and allows the caller to continue writing the value.
Key concepts
This is a read-only borrow. Several can exist simultaneously at the same point in time.
fn len_of(s: &String) -> usize { s.len() }
let s = String::from("Rust");
let n = len_of(&s); //Ownership Transfer
This is a modifiable lease. Only one can exist at any one time, and no other references (immutable or mutable) can be created while a mutable reference is alive.
fn append_excl(s: &mut String) { s.push('!'); }
let mut s = String::from("hello");
append_excl(&mut s);
Only one of the following two is allowed at the same time:
- immutable reference multiple
- variable reference one
Thanks to this rule, data races are blocked at the compilation stage. Thanks to non-lexical lifetimes (NLL) since Rust 1.31, references are automatically disabled after "last used", allowing new references to be created to the same variable.
The compiler blocks dangling references to values that have already been dropped. If you try to return a reference to a local variable as is from a function, you will get a lifetime error — this is covered in more detail in Part 9.
See as example
//Part 7 Example 1: Shared References (
//
//Once a reference is received, ownership does not transfer, so the caller can continue to use the variable.
fn len_of(s: &String) -> usize {
s.len()
} //Here, s is a borrowed reference and is not dropped.
fn main() {
let s = String::from("Hello, Rust!");
let n = len_of(&s); //Create reference: `
println!("The length of \"{s}\" is {n}");
//You can have multiple references at the same time (if they are immutable).
let r1 = &s;
let r2 = &s;
println!("Two references to the same value: {r1} / {r2}");
}
//Part 7 Example 2: Variable Reference (
//
//To create a mutable reference, the source must also be `mut`,
//There can be **only** a mutable reference at any given time.
fn append_excl(s: &mut String) {
s.push('!');
}
fn main() {
let mut greeting = String::from("hello");
append_excl(&mut greeting);
append_excl(&mut greeting);
println!("{greeting}");
//Creating and using mutable references directly
let r = &mut greeting;
r.push_str(" Rust");
println!("{r}");
//From this point on, r will no longer be used, so you can create another reference.
println!("(original) {greeting}");
}
//Part 7 Example 3: Borrowing Rules — At the same time
//(a) 1 mutable reference or
//(b) N immutable references
//Only one of the two is allowed.
fn main() {
let mut v = vec![1, 2, 3];
//Multiple immutable references are OK
let a = &v;
let b = &v;
println!("a = {a:?}, b = {b:?}");
//The above two references are last used here → Disabled thereafter
//After disabled, new mutable references can be created. There is.
let m = &mut v;
m.push(4);
println!("m = {m:?}");
//The compiler also prevents dangling references:
// fn dangle() -> &String {
// let s = String::from("oops");
//
// }
}
Common Mistakes
A: That's right. &str is both a slice of String and a string literal, making it more general-purpose. We'll cover slicing in detail in the next episode. First of all, in this episode, I used &String to learn the borrowing itself.
A. Check if the original variable is declared as mut. To create &mut s, you must declare it as variable like let mut s = ...;.
A. Not at the same time. After the first mutable reference is consumed (finally read), the NLL allows you to create a new mutable reference.
A. The reference itself is a simple pointer, so it behaves like a small Copy type. However, the owner of the “value pointed to by the reference” is still the original variable.
Cleanup
&Tis a read-only borrow, and&mut Tis a modifiable borrow.- 1 mutable or N immutable at any one time — no simultaneous allowed.
- A function that receives a reference allows the caller’s value to be used as is.
- The compiler automatically blocks dangling references.
Try it yourself
Solve the assignment below. The reference solution is shown below.