DEV Community

Cover image for How Rust memory management work to beginners.
Canhassi
Canhassi

Posted on

How Rust memory management work to beginners.

Do you ever thought about what happens to your RAM when you run a program? And how the ways you write code can interfere with many other things in your system?

That article will help you to understand more about management memory and HOW RUST WORKS THIS SUBJECT.

Table of Contents

1. Stack and Heap

Before you learn what rust do, you have to learn some concepts. In general we have two types of memory, that are called: Stack and Heap. Now let's introduce them.

1.1 Memory: Stack

Stack, as the name says, works like a stack, following the principle of "Last In, First Out" (LIFO). Maybe it will be easier explain it in steps:

  • Imagine a stack of dishes;
  • The first dish you put in is the last to be removed;
  • When a function is called, a block of memory is 'stacked' on top of the stack;
  • When the function ends, this block is "unstacked", freeing this memory.

Normally, the values that will be stored on the stack is known by the compiler (in the compiling time), because it knows how much memory is needed to store. This process occurs automatically, all values are deleted from memory.

Below is an example of this:

fn main() {
    let number = 12; // at this moment the variable has created

    println!("{}", number); // 12 
} // When the owner (main function) goes out of scope, the value will be dropped

Enter fullscreen mode Exit fullscreen mode

In Rust we can create some scopes just by using {}, and this adds layer into our stack with a limited lifetime. After your goes out from that specific scope, the memory is cleared and you lost the information related. A good and simple example of it is:

fn main() {
    {
        let number = 12;

        println!("{}", number); // 12
    }

    println!("{}", number); // Cannot find value `number` in this scope
}
Enter fullscreen mode Exit fullscreen mode

1.2 Memory: Heap

In a few words: the heap memory is a free memory space to allocate data that may change.

Imagine that you need to store some variable after starting your program, this variable doesn't have a fixed size known at compile time since it can have size variations or is a direct allocation in memory.

If one of the possibilities above matches, we know that we have Heap memory instead Stack. Heap has a more flexible memory and a large space. Take a look:

let number = Box::new(12); // alocate a integer in heap

let name = String::from("Canhassi"); // alocate a String in heap
Enter fullscreen mode Exit fullscreen mode

In Rust we have two types of string, &str and String.

  • &str: has a fixed size based on the text written;
  • String: has a size that can be increased, decreased, and deleted.

... and that's why String is stored on the Heap, and &str on the stack.

One way to free heap memory is: when a variable that stores something that is on the heap leaves the scope of the function (reaches the end of the function), it will be freed in the same way as the stack.

Okay, now we have a clear image on both memory types, but what the difference to manage memory between Stack and Heap? Let's see these diferences!

2. Borrow checker

Borrow Checker is the part of the Rust compiler that checks and ensures that ownership, borrowing, and lifetime rules are respected.

I'm gonna be honest with you: I had a some issues to understanding how this works at beginning, and I see that is common among new Rustaceans . But don't worry, friend of mine. I'll teach you on the best way possible. But first, let's take a look the code below:

fn main() {
    let name = String::from("Canhassi"); // Creating a string variable

    print_name(name); // calling the print_name function passing the variable

    println!("{}", name); // Error: name borrowed to print_name()
}

fn print_name(name: String) {
    println!("{}", name); // print "Canhassi"
}
Enter fullscreen mode Exit fullscreen mode

If you run the compiler with that code you gonna see the following error:

borrow of moved value: name
Enter fullscreen mode Exit fullscreen mode

When we call the print_name function, the name variable will be moved to another scope and that scope will be the new owner of it. And the rule of owner goes out scope will applied again. Borrow Checker ensures that once ownership is transferred, the original variable can no longer be used to access that value. this happened in the code above.

Another way that you can use the original variable is using references like that.

fn main() {
    let name = String::from("Canhassi"); // Creating a string variable

    print_name(&name); // calling the print_name function passing the variable

    println!("{}", name); // print "Canhassi"
}

fn print_name(name: &String) {
    println!("{}", name); // print "Canhassi"
}
Enter fullscreen mode Exit fullscreen mode

PS: These rules of borrow checker work just with objects allocated in heap.

3. Errors prevention

The Borrow Checker is fundamental to ensuring Rust's memory safety without the need for a garbage collector.

In other languages like C and C++, we (as developers) have to deallocate memory manually with some functions. C++ is known for allowing memory management errors, including memory leaks. C++ itself does not cause memory leaks. Instead, C++ provides a great deal of flexibility and control to the programmer, and this freedom can lead to errors if not used correctly.

A famous error is Undefined Behavior, for example, imagine a function that return e reference of a variable like that

fn main() {
    let number = foo(); // calling foo function
}

fn foo() -> &i32 {
    let number = 12; // creating a var number

    &number // try return a reference of var number
}
Enter fullscreen mode Exit fullscreen mode

This code doesn't work because the Rust compiler has restrict rules with memory management. We can't return a reference of var number because the scope of this function will go out, and the var number will disappear, so Rust doesn't let this happen. In C++ we can do that, and it allows the notorious memory leaks.

I think that is kinda cool to know that the Rust compiler avoids this type of error... If this subject is new for you, I can tell that memory leaks can cost a lot of money and is truly hard to fix it, since is never only one memory leak.

Other example is, Double free, that occur when you try free twice the same object for example in C code.

char* ptr = malloc(sizeof(char));

*ptr = 'a';
free(ptr);
free(ptr);
Enter fullscreen mode Exit fullscreen mode

This subject is really extensive and there's many possibilities to generate errors like that when you're working in other languages. But, I'll let you do your own research and make sure to tell me more about on this post comments!

Undefined Behavior
Double Free

4. Conclusion

I wrote this article with the aim of presenting in a more general way how memory management works with Rust. But I recommend for a more complete knowledge the chapter 4 of The Rust Programming Language book. There you will have more examples and more explanations of this content.

I really hope this article was helpful!! 🦀🦀

My twitter
Linkedln

Top comments (26)

Collapse
 
danielhe4rt profile image
Daniel Reis

I was not even aware about this terminology before. Stack and Heap seems to be a really common thing for low level languages, but since I'm new to Rust this article helped me a lot.

Thanks for the contribution!

Collapse
 
renanvidal profile image
Renan Vidal Rodrigues

OMG Canhas? Big fan here! Great content man!

Collapse
 
karol11 profile image
Andrey S Kalmatskiy

Rust borrow checker does not help with managing object hierarchies in heap. It works only around stack and helps only with synchronous function calls. It doesn't work with asynchronous and multithreaded code. Also Rust does not prevent memory leaks.

Collapse
 
lliw profile image
William Rodrigues

While Rust's memory management may pose initial challenges for beginners, it proves remarkably powerful once mastered. And this article helped me to make this.

Collapse
 
samucadev profile image
SamucaDev

Awesome!!

Collapse
 
nicolab profile image
Nicolas Talle

Good articles.

In multi thread, pay attention to race conditions with references like print_name(&name);

Collapse
 
troiter profile image
John

Technically, &str is simply a reference (an address) which is a fixed size that may point to dynamically created data or static. The only thing on the stack is the address itself, not the actual data, because the slice can also refer to gigabytes of data. I just wanted to make sure there was no confusion and people assumed the slice was a value type. Nice article, btw!

Collapse
 
canhassi profile image
Canhassi

Thank you for the explanation <3

Collapse
 
orionth1 profile image
Matheus Emanoel

Nicee Canhas

Collapse
 
phenriquesousa profile image
Pedro Henrique

Such an inspiration

Collapse
 
nicolus profile image
Nicolus • Edited

I have never used Rust and don't really intend to, but this article was still fascinating, especially since I suspect those concept are relevant in many other languages.

Thanks !