Rust is a statically typed & compiled systems programming language like C & C++. This is quite simple compared to the memory structure we saw in the previous chapters for JVM, V8 and Go.
As you can see there is no generational memory or any complex substructures since Garbage Collection(GC) is not involved. The reason for this is that Rust manages memory as part of program execution during runtime using the Ownership model rather than using any kind of GC.
This is the biggest block of memory and the part managed by Rust ’s Ownership model. A Box
Now that we are clear about how memory is organized let’s see how Rust uses Stack and Heap when a program is executed. Let’s use the below Rust program, the code is not optimized for correctness hence ignore issues like unnecessary intermediary variables and such, the focus is to visualize Stack and Heap memory usage.
Note: you can find the code I used to identify where a value ends up here These rules are checked by the compiler at compile-time and the freeing of memory happens at runtime along with program execution and hence there is no additional overhead or pause times here.
Rust compiler has a borrow checker that statically ensures that references point to valid objects and ownership rules are not violated. The lifetime of variables is another very important concept to make the ownership model work.
It is a construct used by the borrow checker in order to ensure that all references to an object is valid. This might sound straight forward but lifetimes get much more complex once functions and structs with references come into play and once they do than we would need to start using lifetime annotations to let the borrow checker know how long references are valid.
Rust has support for pointers and lets us reference and reference them using & and * operators. Box, String and DEC are examples of smart pointers in Rust.
This post should give you an overview of the Rust memory structure and memory management. Unlike Garbage collected languages, where you need not understand the memory management model to use the language, in Rust, it is required to understand how Ownership works in order to write programs.
This post is just a starting step, I recommend that you dive into the Rust documentation to learn more about these concepts. I hope you had fun learning this, stay tuned for the next post in the series.
I always try out different programming languages and always interested to learn internals by cherry-picking its feature; one at a time. The story behind all those design choices is always exciting and something worth to pursue.
Over the years I have learned a lot of programming languages, however, I am not an expert on any of them; the fact is I never intended to be. It is the concept that Rust compiler use under the hood for memory safety that helped to solve null pointer error that most of the other low-level programming languages face.
The designers of the language wanted Rust to be safe, low level, efficient, and fast. For a lot of years, programmers had to be careful while dealing with low-level stuff, languages like C and C++ allowed low-level control but developers had to write a lot of code to ensure memory safety and avoid memory leaks.
Now, Rust came to existence, it’s basically C++ with newer syntax that guides programmer towards safety still allowing low-level control, how does it do it? The concept like ownership and borrowing (I will write a new blog on this topic) helps it to achieve the memory safety that other low-level language could not incorporate.
Before taking about ownership, I want to mention concepts like mallow and garbage collector. Let me explain a little about these problems, double free is a problem where two sections of the code are pointing to a same block of memory and both of them tries to free the allocated memory.
When a function execution is over the respective variable and values stored on the stack is thrown away hence cleaning up the memory resources captured. Hell, no copying a large collection of data is inefficient and takes up a lot of space in memory.
We don’t copy the actual data rather we copy the memory reference (pointer) making code much more efficient, fast and saving a lot of space on memory. Now we have created two variables pointing to the same memory location at the heap leading towards a problem I have mentioned above like double free and dangling pointers.
The same happens to a data structure on Rust, at any given instance of time only one stack frame can own this data structure, this has a nice implication as we can safely free the data structure from heap once the stack frame that owns it is out of the stack. The same case can happen here as well, like, what if two variables are pointing to the same data structure.
The same thing happens on Rust when two variables are pointing to the same data structure. Stack: It’s a special region of your computer’s memory that stores temporary variables created by each function.
When a function exits, all of its variables are popped off of the stack (and hence lost forever). While the value of more complex objects or types that could grow in size are stored in the heap memory.
MyString is stored in stack because we know size of the string literal at compile time. However sometimes we don’t know the size at compile time and it grows while running the program.
The advantage of using the stack to store variables, is that memory is managed for you. In a C world, to allocate memory on the heap, you must use mallow() or callow() (built-in C functions).
The same thing happen : when a variable goes out of scope, Rust calls a special function for us. Rust calls drop automatically at the closing curly bracket.
Bind the value 5 to x; then make a copy of the value in x and bind it to y. we have two variables, x and y, and both equal 5. Ownership isn’t affected (we make a copy). The Boolean type, built, with values true and false.
Representation in memory after s1 has been invalidated We would say that s1 was moved into s2 (shallow copy). Rust will never automatically create “deep” copies of your data.
This works just fine but the operation s2 = s1 could be very expensive in terms of runtime performance if the data on the heap were large. Borrowing without loosing ownershipLikewise, the signature of the function uses & to indicate that the type of the parameter s is a reference.
The benefit of having this restriction is that Rust can prevent data races at compile time. Two or more pointers access the same data at the same time (multiple readers).
This code is fine : the scopes of the immutable references r1 and r2 end after the print! Also, this code will works fine : brackets create a new scope, allowing for multiple mutable references, just not simultaneous ones.
In this story, I discovered how Rust manages memory : copy, clone, variable scope, function scope, borrowing, mutable & immutable references restrictions, dangling pointers prevention … Primitives types are Copy, and they are stored in stack because there size is known at compile time.