Skip to content

Memory Management in Rust — Stack, Heap & RAII

DodaTech Updated 2026-06-21 6 min read

In this tutorial, you'll learn about Memory Management in Rust. We cover key concepts, practical examples, and best practices.

Rust's memory management is built on the stack and heap with the RAII (Resource Acquisition Is Initialization) pattern, ensuring every resource is freed exactly once, deterministically, without a garbage collector.

What You'll Learn

In this tutorial, you'll learn how Rust manages memory on the stack and heap, how the RAII pattern works, and how resource cleanup is guaranteed at compile time.

Why It Matters

Memory bugs — leaks, double frees, use-after-free — account for roughly 70% of critical CVEs in C and C++ programs. Rust's memory management model eliminates these bugs entirely. Durga Antivirus Pro relies on this guarantee for its real-time file scanning engine where a single memory corruption could compromise the entire system.

Real-World Use

Database engines, browser rendering pipelines, game engines, and embedded firmware all manage memory intensely. The Rust compiler prevents memory errors while maintaining performance comparable to hand-optimized C++.

flowchart LR
    subgraph Stack
        F1[Frame: main]
        F2[Frame: foo]
        F3[Frame: bar]
    end
    subgraph Heap
        B1[Box]
        V1[Vec]
        S1[String]
    end
    F2 -->|owns| B1
    F3 -->|owns| V1
    F1 -->|owns| S1
    style Stack fill:#e6f3ff
    style Heap fill:#fff3e6
â„šī¸ Info

Prerequisites: Rust basics. Understanding of the Rust systems programming overview helps.

Stack vs Heap

The stack is fast and deterministic. Each function call creates a frame that is popped when the function returns. The heap is more flexible but requires explicit allocation and deallocation.

fn main() {
    // Stack-allocated: size known at compile time
    let x: i32 = 42;
    let y: f64 = 3.14;
    let b: bool = true;

    // Heap-allocated: dynamic size, managed by Box
    let heap_val = Box::new(100_i32);
    let heap_string = String::from("Hello, heap!");

    println!("Stack: x={}, y={}, b={}", x, y, b);
    println!("Heap: {}, {}", heap_val, heap_string);
    // Memory freed automatically when variables go out of scope
}

Expected output:

Stack: x=42, y=3.14, b=true
Heap: 100, Hello, heap!

The RAII Pattern

Resource Acquisition Is Initialization means resources are acquired when an object is created and released when it is destroyed. In Rust, this maps perfectly to the ownership system.

struct Resource {
    name: String,
}

impl Resource {
    fn new(name: &str) -> Resource {
        println!("Acquiring resource: {}", name);
        Resource { name: String::from(name) }
    }
}

impl Drop for Resource {
    fn drop(&mut self) {
        println!("Releasing resource: {}", self.name);
    }
}

fn main() {
    let r1 = Resource::new("database connection");
    let r2 = Resource::new("file handle");
    println!("Working with resources...");
    // r1 and r2 are dropped here in reverse order (r2 then r1)
}

Expected output:

Acquiring resource: database connection
Acquiring resource: file handle
Working with resources...
Releasing resource: file handle
Releasing resource: database connection

Ownership Transfer

When a value is moved, ownership transfers to the new owner. The old owner can no longer use the value.

fn main() {
    let s1 = String::from("hello");
    let s2 = s1; // ownership moved from s1 to s2
    // println!("{}", s1); // COMPILE ERROR: s1 is no longer valid
    println!("s2 = {}", s2); // s2 is the new owner

    // Transfer via function call
    let s3 = String::from("world");
    take_ownership(s3);
    // println!("{}", s3); // COMPILE ERROR: ownership moved into function
}

fn take_ownership(s: String) {
    println!("Took ownership of: {}", s);
} // s is dropped here

Expected output:

s2 = hello
Took ownership of: world

Box for Heap Allocation

Box<T> is the simplest way to allocate on the heap. It is useful for recursive types and large data that should not be copied.

#[derive(Debug)]
enum List {
    Cons(i32, Box<List>),
    Nil,
}

fn main() {
    // Recursive type requires Box for known size
    let list = List::Cons(1, Box::new(List::Cons(2, Box::new(List::Cons(3, Box::new(List::Nil))))));
    println!("List: {:?}", list);

    // Large allocation on heap
    let large_vec = Box::new([0u8; 1024 * 1024]); // 1MB on heap
    println!("Allocated 1MB on heap, first byte: {}", large_vec[0]);
}

Expected output:

List: Cons(1, Box(Cons(2, Box(Cons(3, Box(Nil))))))
Allocated 1MB on heap, first byte: 0

Common Mistakes

1. Assuming Heap Allocation Is Automatic

Primitives and fixed-size arrays go on the stack. Only types with dynamic size (String, Vec, Box) use the heap. Do not assume every allocation is on the heap.

2. Forgetting the Drop Order

Values are dropped in reverse declaration order (LIFO). Resources that depend on each other must be declared in the correct order to avoid use-after-free in drop implementations.

3. Unnecessary clone() Instead of Borrowing

Cloning heap-allocated data like String or Vec is expensive. Prefer references when you only need read access.

4. Using Box::new() for Large Stack Types

Box::new() allocates on the heap but the value is first created on the stack and then moved. Use Box::new() only for values that need heap allocation, not as a performance optimization.

5. Not Implementing Drop for Resource Types

If you create a type that manages a system resource (file handle, socket, mutex), implement Drop to release it. Relying on the default drop may not handle cleanup properly.

Practice Questions

1. What is the difference between stack and heap allocation? Stack allocation is LIFO, fast, and requires compile-time known sizes. Heap allocation is flexible, supports dynamic sizes, but has allocation overhead. Rust manages both automatically.

2. What does RAII stand for and how does Rust implement it? Resource Acquisition Is Initialization. Rust implements it through ownership and Drop: resources are acquired in constructors and released when the owner goes out of scope, calling Drop::drop().

3. What happens when ownership of a value is moved? The original owner can no longer access the value. The new owner is responsible for cleanup. This prevents double frees and use-after-free errors.

4. When should you use Box<T>? For recursive types (linked lists, trees), allocating large data on the heap, type erasure (trait objects), and reducing stack frame size by moving large values to the heap.

5. Challenge: Create a DatabaseConnection struct that prints "Connected" on creation and "Disconnected" on drop, then demonstrate ownership transfer between functions.

Mini Project: RAII Logger

use std::fs::File;
use std::io::Write;

struct Logger {
    file: Option<File>,
}

impl Logger {
    fn new(path: &str) -> Logger {
        let file = File::create(path).expect("Cannot create log file");
        println!("Logger opened: {}", path);
        Logger { file: Some(file) }
    }

    fn log(&mut self, message: &str) {
        if let Some(ref mut f) = self.file {
            writeln!(f, "{}", message).expect("Write failed");
        }
        println!("LOG: {}", message);
    }
}

impl Drop for Logger {
    fn drop(&mut self) {
        if self.file.is_some() {
            println!("Logger closing file");
        }
    }
}

fn main() {
    let mut logger = Logger::new("app.log");
    logger.log("System initialized");
    logger.log("Memory management tutorial complete");
}

FAQ

Does Rust have a garbage collector?

No. Rust uses ownership and RAII for deterministic memory management. Resources are freed when their owner goes out of scope, which is known at compile time. This avoids GC pauses and unpredictable latency.

What is the difference between Box and Rc?

Box<T> provides single ownership with heap allocation. Rc<T> provides shared ownership via reference counting. Use Box for exclusive ownership and Rc for shared read-only access within a single thread.

How does Rust prevent memory leaks?

Rust prevents most memory leaks through ownership. When a value goes out of scope, its Drop implementation runs. However, reference cycles with Rc and RefCell can cause leaks. Use Weak to break cycles.

Rust Systems Overview
Rust Ownership
Borrowing & References

What's Next

Now that you understand memory management, learn Rust Ownership for the complete guide, then master Borrowing & References.

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro