Last time we talked about Ownership. Rust’s way of making sure every value has exactly one owner. But if everything had to be moved every time you passed it somewhere, writing code would be a nightmare. That’s where borrowing comes in.
In Rust, you can lend a value to a function or variable without giving up ownership. You do this with references: &T for an immutable borrow, and &mut T for a mutable one.
The JavaScript comparison
In JavaScript, when you pass an object to a function, you’re handing over a reference automatically. You don’t think about it. You can mutate it, pass it to ten other places at the same time, and nothing stops you. Rust has references too, but with hard rules enforced at compile time:
- You can have any number of immutable references (
&T) at the same time: read-only, no problem. - You can have exactly one mutable reference (
&mut T) and while it exists, zero immutable ones.
That’s it. Two rules. But they eliminate an entire class of bugs: no data races, no “who mutated this?” debugging sessions.
Why does this feel weird at first?
Because in JS/TS, mutation is everywhere and implicit. You never declare intent. Rust forces you to be explicit: are you reading, or are you writing? And if you’re writing, you’re doing it alone. The compiler is essentially your strict code reviewer who never lets a shared mutable state slip through.
const in JS only protects the binding. The value itself is still wide open — you can’t stop someone from mutating the object you passed them. Rust’s &T goes deeper: it protects the value itself. If you only have an immutable reference, mutation is off the table, the compiler guarantees it.
Once it clicks, you stop seeing the borrow checker as an enemy and start seeing it as the teammate who catches the race condition before it ever reaches production.
Example
fn main() {
let mut message = String::from("Hello");
let r1 = &message; // immutable borrow ✓
let r2 = &message; // another one — totally fine ✓
println!("{} {}", r1, r2); // r1 and r2 are no longer used after this point
let r3 = &mut message; // mutable borrow ✓ (previous borrows are done)
r3.push_str(", world!");
println!("{}", r3);
}
Compare this to JS, where there are no rules at all:
let message = { text: "Hello" };
const r1 = message; // anyone can read
const r2 = message; // anyone can mutate
r2.text = "oops"; // r1 just got silently changed too 👀
In Rust, that silent mutation simply cannot happen. The compiler won’t allow a mutable reference to exist alongside any other reference.