Skip to content

Borrowing & References

TypeScript passes references freely — Rust tracks them

Section titled “TypeScript passes references freely — Rust tracks them”

In TypeScript, passing an object to a function gives the function a reference. Both the caller and the callee can read and write the object simultaneously. The language imposes no restriction on how many references exist or what they can do.

Rust’s borrow checker enforces strict rules about references at compile time. The rules prevent data races, dangling pointers, and use-after-free — all at zero runtime cost.

A shared reference (&T) lets you read a value without taking ownership. Many shared references can exist at the same time.

TypeScript
// TypeScript — just passing a reference (no rules enforced)
function getLength(s: string): number {
return s.length;
}
const greeting = "hello";
const len = getLength(greeting);
console.log(greeting, len); // both still valid
Rust
fn get_length(s: &String) -> usize {
s.len() // we borrow s — we cannot move out of it
}
fn main() {
let greeting = String::from("hello");
let len = get_length(&greeting); // lend greeting to the function
println!("{} has {} chars", greeting, len);
// greeting is still valid here — we only borrowed it
}

A mutable reference (&mut T) lets you both read and modify a value. But Rust enforces an exclusive rule: while a &mut reference exists, no other reference — shared or mutable — may exist at the same time.

TypeScript
// TypeScript — no restriction on simultaneous read + write
function appendWorld(s: { value: string }): void {
s.value += ", world";
}
const msg = { value: "hello" };
appendWorld(msg);
console.log(msg.value); // "hello, world"
Rust
fn append_world(s: &mut String) {
s.push_str(", world");
}
fn main() {
let mut s = String::from("hello");
append_world(&mut s);
println!("{}", s); // "hello, world"
// Multiple shared refs are fine — no mutation happening
let r1 = &s;
let r2 = &s;
println!("{} and {}", r1, r2);
}

This is the most common error new Rust developers encounter. The code below tries to hold a shared reference and a mutable reference at the same time:

fn main() {
let mut s = String::from("hello");
let r1 = &s; // shared borrow begins
let r2 = &s; // another shared borrow — still fine
let r3 = &mut s; // ERROR: cannot borrow `s` as mutable because it is also borrowed as immutable
println!("{}, {}, {}", r1, r2, r3);
}
// error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
// --> src/main.rs:6:14
// |
// 4 | let r1 = &s;
// | -- immutable borrow occurs here
// 6 | let r3 = &mut s;
// | ^^^^^^ mutable borrow occurs here
// 7 | println!("{}, {}, {}", r1, r2, r3);
// | -- immutable borrow later used here

The fix is simple: make sure shared and mutable borrows do not overlap. End the shared borrows first (they end when they are last used), then take the mutable borrow:

fn append_world(s: &mut String) {
s.push_str(", world");
}
fn main() {
let mut s = String::from("hello");
append_world(&mut s);
println!("{}", s);
// multiple shared refs are fine
let r1 = &s;
let r2 = &s;
println!("{} and {}", r1, r2);
}
How many mutable references (`&mut T`) to the same value can exist at the same time in Rust?
What does `&T` mean in a function signature?
Which borrow checker rule prevents data races at compile time?