Rust Borrowing
Immutable References
fn print_length(s: &String) {
println!("length: {}", s.len());
} // s goes out of scope, but it's a reference — nothing is dropped
fn main() {
let s = String::from("hello");
print_length(&s); // borrow s
println!("{s}"); // s is still valid
}
&s creates a reference. The function borrows the value without taking ownership. Multiple immutable references can coexist.
Mutable References
fn add_world(s: &mut String) {
s.push_str(", world");
}
fn main() {
let mut s = String::from("hello");
add_world(&mut s);
println!("{s}"); // "hello, world"
}
Mutable references require the variable to be declared mut. Only one &mut T is allowed at a time.
The Borrowing Rules
let mut s = String::from("hello");
// Rule 1: Any number of immutable references
let r1 = &s;
let r2 = &s;
println!("{r1}, {r2}"); // OK
// Rule 2: Exactly one mutable reference, no immutable refs active
let r3 = &mut s;
r3.push_str("!");
// println!("{r1}"); // ERROR: can't use r1 while r3 exists
// After r3's last use, new borrows are fine
println!("{s}");
These rules prevent data races at compile time. You either have many readers or one writer — never both simultaneously.
String Borrowing
// &str is a borrowed string slice — a view into string data
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &b) in bytes.iter().enumerate() {
if b == b' ' {
return &s[..i];
}
}
s
}
let owned = String::from("hello world");
let word = first_word(&owned); // &String coerces to &str
println!("{word}"); // "hello"
let literal = "hello world"; // &str (string literal)
let word = first_word(literal); // already &str
Accept &str in function signatures — it accepts both &String (via deref coercion) and string literals. Return String when the function creates new data; return &str when it returns a view.
Slice Borrowing
fn sum(numbers: &[i32]) -> i32 {
numbers.iter().sum()
}
let v = vec![1, 2, 3, 4, 5];
let total = sum(&v); // &Vec<i32> coerces to &[i32]
let partial = sum(&v[1..4]); // borrow a sub-slice
&[T] is a slice reference — a view into contiguous memory. Like &str for strings, accept &[T] to be flexible about what callers can pass.
Reborrowing
let mut v = vec![1, 2, 3];
let r = &mut v;
// The compiler "reborrows" r for the function call
r.push(4); // uses &mut *r implicitly
r.push(5); // r is still usable
println!("{:?}", r);
Mutable references are automatically reborrowed when passed to functions. This means you can call multiple methods on &mut T without explicitly creating new references each time.