Borrowing in One Page
The problem borrowing solves
We want this to work:
fn main() {
let name = String::from("Ada");
print_name(name);
println!("{name}"); // we'd like to still use name here
}
fn print_name(n: String) {
println!("hello, {n}");
}
It does not work. print_name(name) moves name into the function. By the time println! on the next line runs, name is gone.
We could fix it by cloning, but cloning a string means copying the bytes, which we do not need. What we actually want is “let the function look at the value, but keep ownership here.”
That is a borrow. You write it with &:
fn main() {
let name = String::from("Ada");
print_name(&name);
println!("{name}"); // still valid — we only lent the name out
}
fn print_name(n: &String) {
println!("hello, {n}");
}
▶ Run this in the Rust Playground
A borrow is "please read this, do not take it". The & is the way you hand the thing over to someone to look at, not to keep.
Two kinds of borrow
There are two borrow shapes in Rust, and they follow one rule between them.
Many readers OR one writer. Never both at once.
&T readers or exactly one &mut T writer. This is the borrow rule.&T— a shared borrow. Read-only. Many allowed at once.&mut T— an exclusive borrow. Read and write. Exactly one allowed at a time, and no&Treaders can exist while it does.
That rule is the whole borrow checker. Everything else follows from it.
The rule in code
This compiles. Many readers, no writers:
fn main() {
let s = String::from("rust");
let a = &s;
let b = &s;
println!("{a} {b} {s}");
}
This also compiles. One writer, no readers:
fn main() {
let mut s = String::from("rust");
let w = &mut s;
w.push_str("!");
println!("{w}");
}
This does not compile. A reader and a writer at the same time:
fn main() {
let mut s = String::from("rust");
let r = &s;
let w = &mut s; // error: cannot borrow `s` as mutable because it is also borrowed as immutable
println!("{r} {w}");
}
▶ Run this in the Rust Playground
Read the error when it shouts at you. It will tell you, to the character, which borrow was already live and what you tried to do.
If one person is reading your notebook and another person starts erasing and rewriting it, what the first person sees is nonsense. Rust catches that at compile time for every single piece of data you own. Data races — the bug category that eats entire engineering quarters — simply cannot compile.
A passing note on lifetimes
Every borrow lives for a certain slice of time — from the moment it is taken to the last moment it is used. Rust tracks these slices automatically. You will not usually annotate them. When you do need to, the notation looks like &'a str and it is covered in Part 3.
For now, take the following on faith: if the value you borrowed from is dropped, any borrow of it stops being valid, and the compiler will refuse to let you use it. That is another bug class — use after free — quietly removed.
String vs &str — the first consequence
This is where Rust’s two string types finally make sense.
String— an owning string. A heap-allocated, growable buffer that you own. Likestd::stringin C++ orStringBuilder.&str— a borrowed view into some text that lives elsewhere. Pronounced “string slice”. Like a read-only window.
When you write "hello" in Rust source, you get a &str — a borrowed view of a literal baked into the program. When you call String::from("hello"), you allocate your own owned copy. Any function that just wants to read some text should take &str.
fn greet(name: &str) {
println!("hello, {name}");
}
fn main() {
greet("Ada"); // passes a &str directly
let name = String::from("Ben");
greet(&name); // &String auto-coerces to &str
}
▶ Run this in the Rust Playground
Default habit: your functions take &str, not String. Callers can pass either — Rust will do the coercion for you. You keep the flexibility, they keep ownership of their data.
Step through the lending version and watch the reference point back at the owner — and the owner survive the call.
Interactive simulation (requires JavaScript): passing &name lends the String to print_name, whose parameter points back at the owner; when the function returns the loan ends and name is still valid.
- Write a function
fn length(s: &str) -> usizethat returns the number of bytes in the string. Call it frommain. - Write a function
fn shout(s: &mut String)that appends"!"to its argument. Call it on alet mut msg = String::from("hi");and print the result. - Write a program that holds both an
&sand an&mut sat the same time. Read the error. It will tell you exactly which line to move.
Now for the other idea Rust is famous for — how errors and missing values are just ordinary data.