Skip to content

Ownership

JavaScript manages memory for you — Rust does not

Section titled “JavaScript manages memory for you — Rust does not”

In JavaScript and TypeScript, every heap value (strings, arrays, objects) is tracked by a garbage collector. You create values freely; the GC figures out when nobody is using them and frees the memory. You never call free(). You never worry about use-after-free.

Rust has no garbage collector. Instead it has ownership: a compile-time system of rules that gives every value exactly one owner. When the owner goes out of scope, the value is automatically freed — no GC needed.

In TypeScript, assigning an object to a new variable creates a second reference. Both variables point to the same data. In Rust, assigning a heap value moves ownership: the original variable is invalidated.

TypeScript
// TypeScript — two references, same object
const a = { name: "Alice" };
const b = a; // b points to the same object
console.log(a.name); // "Alice" — a is still valid
console.log(b.name); // "Alice" — both work
// The GC will free the object when BOTH a and b go out of scope.
// You cannot control when.
Rust
fn main() {
// Rust — String is a heap value
let a = String::from("Alice");
let b = a; // ownership MOVES from a to b
// println!("{}", a); // error[E0382]: use of moved value: `a`
println!("{}", b); // only b is valid now
// When b goes out of scope at the end of main(),
// Rust automatically frees the String data. No GC needed.
}

Passing a heap value to a function moves it into that function. The caller can no longer use it unless the function returns it back.

TypeScript
// TypeScript — passing an object does not move ownership
function greet(name: string) {
console.log(`Hello, ${name}`);
}
const s = "world";
greet(s);
console.log(s); // still valid — JS passes by reference for objects
Rust
fn greet(name: String) {
// name is owned here
println!("Hello, {}", name);
} // name is dropped here
fn main() {
let s = String::from("world");
greet(s); // ownership moves into greet()
// println!("{}", s); // error[E0382]: use of moved value: `s`
}
fn takes_ownership(s: String) {
println!("got: {}", s);
} // s dropped here
fn makes_copy(n: i32) {
println!("copy: {}", n);
} // n dropped here, but i32 is Copy so the caller's copy is unaffected
fn main() {
let s = String::from("hello");
takes_ownership(s);
// println!("{}", s); // would be a compile error — s was moved
let x = 5;
makes_copy(x);
println!("x is still valid: {}", x); // fine — i32 is Copy
}
What happens when you assign a `String` to a new variable in Rust?
When is a heap-allocated value freed in Rust?
What must you do to use a `String` in a function AND still use it afterwards in the caller?
Which rule is NOT one of Rust's three ownership rules?