Memory Management in Rust
In this lesson, you will learn how memory management works in Rust. Unlike many programming languages, Rust manages memory without using a garbage collector.
Instead, Rust uses a unique system based on ownership, borrowing, and lifetimes to ensure memory safety at compile time.
Why Memory Management Matters
Memory management is responsible for allocating and freeing memory efficiently. Poor memory management can cause bugs such as:
- Memory leaks
- Dangling pointers
- Data races
- Program crashes
Rust prevents these issues by enforcing strict rules at compile time.
Stack vs Heap
Rust uses two main memory regions: the stack and the heap.
Stack Memory
The stack stores values with a known, fixed size. It is fast and automatically managed.
let x = 10;
let y = 20;
Variables like integers and booleans are usually stored on the stack.
Heap Memory
The heap stores data whose size is unknown at compile time or needs to live beyond a single scope.
let s = String::from("Rust");
Heap allocation is slower than stack allocation, but it allows for flexible data sizes.
Ownership and Memory
Ownership is the core mechanism Rust uses to manage heap memory.
When a variable owns data, Rust automatically frees that memory when the variable goes out of scope.
{
let text = String::from("Hello");
println!("{}", text);
} // memory is freed here
Move Semantics
When ownership is transferred from one variable to another, this is called a move.
let s1 = String::from("Rust");
let s2 = s1;
// println!("{}", s1); // error
After the move, the original variable is no longer valid.
Cloning Data
If you want to make a deep copy of heap data,
you must explicitly use clone().
let s1 = String::from("Rust");
let s2 = s1.clone();
println!("{}", s1);
println!("{}", s2);
Automatic Memory Cleanup
Rust automatically calls the drop function
when a value goes out of scope.
This guarantees deterministic memory cleanup without a garbage collector.
References and Borrowing
References allow you to access data without taking ownership.
let s = String::from("Rust");
let len = calculate_length(&s);
fn calculate_length(text: &String) -> usize {
text.len()
}
The original owner retains responsibility for freeing the memory.
Mutable vs Immutable Borrowing
Rust enforces strict rules:
- Multiple immutable references are allowed
- Only one mutable reference is allowed at a time
- Mutable and immutable references cannot coexist
Memory Safety Without Garbage Collection
Rust’s memory model ensures:
- No use-after-free errors
- No double frees
- No data races
All these guarantees are enforced at compile time.
📝 Practice Exercises
Exercise 1
Create a variable inside a scope and observe when memory is freed.
Exercise 2
Demonstrate a move operation using a String.
Exercise 3
Clone a string and use both variables.
Exercise 4
Write a function that borrows a string and prints its length.
✅ Practice Answers
Answer 1
{
let x = String::from("Scoped");
} // memory freed here
Answer 2
let a = String::from("Move");
let b = a;
Answer 3
let a = String::from("Clone");
let b = a.clone();
println!("{}", a);
println!("{}", b);
Answer 4
fn length(s: &String) -> usize {
s.len()
}
What’s Next?
In the next lesson, you will learn about smart pointers and how Rust provides advanced memory management abstractions.