The ownership model: move, copy, borrow
Absolutely. The ownership model is the heart of Rustβs memory safety without garbage collection. It eliminates memory bugs at compile timeβnot by runtime checks, but by enforcing strict rules about how data is accessed and transferred.
Letβs break down the three core concepts: Move, Copy, and Borrowβwith clear examples and practical implications.
π Core Principle:
Each piece of data has exactly one owner. When the owner goes out of scope, the data is automatically cleaned up.
This prevents memory leaks and use-after-free bugs by design.
1. π Move β Transfer of Ownership
In Rust, assignment transfers ownership by default (unlike C++ or Go). The original variable is invalidated.
Example:
fn main() {
let s1 = String::from("Hello");
let s2 = s1; // π `s1` is MOVED to `s2`
// println!("{}", s1); // β COMPILE ERROR!
// "value borrowed here after move"
println!("{}", s2); // β
OK
}
Why?String owns heap-allocated data. If both s1 and s2 pointed to it, both would try to free it β double-free crash.
Rust prevents this by invalidating s1 on move.
β Safety: No dangling pointers. No double-frees.
β οΈ Effect: You canβt accidentally use stale data.
2. π Copy β Cheap, Implicit Duplication
Some types are automatically copied on assignment (instead of moved) because theyβre cheap and live entirely on the stack.
These types implement the Copy trait (e.g., integers, booleans, floats, tuples of Copy types).
Example:
fn main() {
let x = 5;
let y = x; // π `x` is COPIED (not moved)
println!("x = {}, y = {}", x, y); // β
Both work!
}
Why is i32 copied but String moved?
-
i32is just 4 bytes on the stack β cheap to duplicate. -
Stringowns a pointer to heap data β expensive and unsafe to duplicate implicitly.
β You never have to call
.clone()for simple values.
π« You cannot implementCopyfor types that manage resources (files, sockets, heap memory).
3. π€ Borrow β Temporary Access Without Ownership
Instead of moving data (which makes the original unusable), you can borrow it using references (&T for read, &mut T for write).
Immutable Borrow (Read-Only)
fn main() {
let s = String::from("Karachi");
let len = calculate_length(&s); // π Borrow `s` (no move!)
println!("Length of '{}' is {}", s, len); // β
`s` still valid
}
fn calculate_length(s: &String) -> usize {
s.len() // `s` is a reference β doesnβt own the data
} // `s` goes out of scope here, but the *owner* still owns it
Mutable Borrow (Read-Write)
fn main() {
let mut s = String::from("Hello");
change(&mut s); // π Mutable borrow
println!("{}", s); // "Hello, Rust!"
}
fn change(s: &mut String) {
s.push_str(", Rust!");
}
π Borrowing Rules (Enforced at Compile Time!)
-
You can have either:
-
One or more immutable references (
&T) -
OR exactly one mutable reference (
&mut T)
-
-
References must always be valid (no dangling pointers).
β This fails to compile:
let mut s = String::from("test"); let r1 = &s; let r2 = &s; let r3 = &mut s; // β Can't borrow mutably while immutable refs exist println!("{}, {}, {}", r1, r2, r3);
π‘ Practical Implications for Reliable Code
| Problem in C/Go | Rustβs Solution |
|---|---|
| Accidentally using freed memory | Move semantics ensure only one owner |
| Unintended data mutation | Immutable by default; mutable borrows are explicit and exclusive |
| Iterator invalidation during loop | Borrow checker rejects code that modifies a collection while iterating |
| Data races in threads | Same rules apply across threads β data races are compile-time errors |
π οΈ When to Use What?
| Scenario | Use |
|---|---|
Passing a String to a function that needs full control |
Move (take_ownership(s)) |
| Just reading a value (e.g., logging, computing length) | Immutable borrow (log(&s)) |
| Modifying data in-place (e.g., appending, sorting) | Mutable borrow (append(&mut vec)) |
| Working with integers, booleans, small structs | Copy (automatic) |
| Need a deep clone (e.g., fork data for parallel processing) | Explicit .clone() |
β Summary
| Concept | What It Does | Safety Benefit |
|---|---|---|
| Move | Transfers ownership; original is invalidated | Prevents double-free, use-after-free |
| Copy | Duplicates simple stack values | Zero-cost, no ownership hassle |
| Borrow | Temporary access via &T or &mut T |
Prevents data races, iterator invalidation, dangling pointers |
π¦ The borrow checker is your allyβitβs strict early so you never debug memory corruption at 3 AM.
Β