Rust’s memory model is built on the idea of ownership. When you assign a value from one variable to another, you might think it’s just a “copy.” But in Rust, for many data types, it’s actually a move.
This move system is what keeps your programs memory-safe without garbage collection.
What Does “Move” Mean? #
In Rust, when you assign or pass a value to another variable or function, the ownership of that value is transferred (moved).
After the move:
- The new variable becomes the owner.
- The old variable can no longer be used.
This ensures that there’s always exactly one owner of any piece of data, preventing double frees or dangling pointers.
Example: Moving a String #
fn main() {
let s1 = String::from("hello");
let s2 = s1; // ownership moves from s1 to s2
println!("{}", s2); // ✅ works
// println!("{}", s1); // ❌ ERROR: s1 is no longer valid
}
Code language: Rust (rust)Why does this happen? #
Stringstores its text on the heap.- If Rust allowed both
s1ands2to point to the same heap memory, freeing them twice would cause a crash. - So, Rust enforces the move rule.
But What About Integers? #
fn main() {
let x = 10;
let y = x; // x is copied, not moved
println!("x = {}, y = {}", x, y); // ✅ works
}
Code language: Rust (rust)i32,bool,char, and other primitive types are stored entirely on the stack.- Copying them is cheap and safe, so Rust automatically gives them the
Copytrait. - That’s why
xis still valid after assigningy = x.
Move in Function Calls #
fn main() {
let name = String::from("Alice");
greet(name);
// println!("{}", name); // ❌ ERROR: name was moved into the function
}
fn greet(person: String) {
println!("Hello, {}!", person);
}
Code language: Rust (rust)- When you pass
nametogreet, its ownership moves into the function. - After the call,
nameis no longer valid inmain.
Borrowing Instead of Moving #
What if you want to use the value without giving up ownership? That’s where borrowing (&) comes in.
fn main() {
let name = String::from("Alice");
greet(&name);
println!("Still own it: {}", name); // works
}
fn greet(person: &String) {
println!("Hello, {}!", person); // borrowed, not moved
}
Code language: Rust (rust)In this example:
- The
greetfunction borrowedname. - The
mainfunction still ownsnameafter the greet function finishes.
Summary #
- Move semantics transfer ownership between variables.
- Heap-allocated types like
Stringare moved by default. - Primitive types (like integers) are copied instead of moved.
- Function arguments can move values into functions.
- Use borrowing (
&) to let functions access values without taking ownership.
This system keeps memory management safe and bug-free — no dangling pointers, no double frees, and no garbage collector needed.