DEV Community

Jen
Jen

Posted on

Understanding Rust Memory Management

Stack & Heap

  • The stack is a region of memory within the RAM allocated to each thread for storing local variables, function parameters, and return addresses
  • The heap is another region of memory within the RAM used for dynamic memory allocation. It's where larger objects or objects with a lifespan that extends beyond the scope of a single function are stored
  • Stack Allocation Data: memory allocation at compile time.
  • Heap Allocation Data: memory allocation at runtime.

Ownership

  • Just think of it like this → when you declare a new variable let name = String::from("Jen") the owner here is the variable called name, owner of what? of the string data “Jen” you dumdum ( which is stored in the Heap)
  • Rust has a single ownership rule, when you transfer the data to another variable, original owner name becomes invalid
fn main() {
    let s1 = String::from("hello");  // `s1` owns the heap-allocated string
    let s2 = s1;  // Ownership is transferred to `s2`, s1 no longer has the right to manage/read the data in the heap
    // println!("{}", s1);  // This line would cause a compile-time error
    println!("{}", s2);  // This works
} // `s2` goes out of scope here, and the memory is freed
Enter fullscreen mode Exit fullscreen mode

Notes:
In some cases, if the variable that’s being re-assigned to a new variable has Types with the Copy Trait, then the value will just be copied example.
let age = 23; // this has i32 type
let age2 = age; // this will just copy the age value and not transfer the ownership
printlin!(”{}, {}”, age, age2) // both age and age2 will work here


Borrowing

  • Borrowing is like asking permission to use the variable somewhere else without transferring its ownership.

Types of Borrowing

  1. Immutable Borrowing: When you borrow a variable immutably, you're asking for permission to read the variable but not modify it. You can have multiple immutable borrows at the same time, as long as there are no active mutable borrows.
  2. Mutable Borrowing: When you borrow a variable mutably, you're asking for permission to modify the variable. There can only be one mutable borrow at a time, and while it's active, no other mutable or immutable borrows can be made.
  3. Scope and Lifetime: The scope of the borrow is limited, meaning the borrowed variable must not outlive the scope of its owner. This is ensured by Rust's borrow checker at compile time, which helps prevent issues like dangling pointers or data races.
  4. Safety: Through borrowing, Rust provides a way to safely and efficiently share data between different parts of a program without the pitfalls of manual memory management found in some other languages.

Why Borrow?

Memory Allocation Optimization

  1. Avoiding Unnecessary Copies: Borrowing allows functions to use data without needing to create a copy of it. This is especially important for large data structures. Without borrowing, every function call or assignment involving these structures would require copying all their contents, which is inefficient in terms of both memory usage and performance.
  2. Efficient Memory Usage: By using references to data, Rust programs can be more memory efficient. Borrowing allows multiple parts of your program to read (and in the case of mutable references, modify) the same piece of data without duplicating it in memory.

Safety

  1. Preventing Data Races: In a concurrent context, borrowing rules ensure that data cannot be simultaneously modified from one place and read or modified from another, which could otherwise lead to data races. Rust's compile-time checks enforce that either a single mutable reference or any number of immutable references can exist for a particular piece of data.
  2. Avoiding Dangling Pointers: Rust's borrowing system ensures that references always point to valid data. The borrow checker at compile time ensures that a reference cannot outlive the data it points to, preventing dangling pointers.
  3. Compile-time Checks: The borrowing rules are enforced at compile time, which means many potential errors (like use-after-free, double free, and data races) are caught before the program even runs.

Slicing

  • Is related to borrowing and mostly being used on array or vector
  • Slicing is a technique to get borrow a portion of array or vector without copying, or transferring ownership
let mut vec = vec![1, 2, 3, 4, 5];
let slice = &mut vec[1..4]; // A mutable slice referencing elements 2, 3, and 4 of vec
slice[0] = 10;
Enter fullscreen mode Exit fullscreen mode

Note:
range syntax is not index..index it’s n_element(..sliced)n_element, where n_elements are excluded

That's it for now, I'll try to post more about my journey on learning rust. Cheers!

Top comments (0)