Stack - Heap

Src: Adafruit.com

We will learn the concept of memory management and how Rust can guarantee memory safety without a Garbage collector.

Like most programming languages, Rust stores data in three different structure parts of your computer memory.

Static/Data Memory

For data that is fixed in size and static (i.e. always available throughout the life of the program).

println!("Hello");

This text's bytes are only ever read from one place and therefore can be stored in this region. Compilers make lots of optimizations with this kind of data, and they are generally considered very fast to use since locations are known and fixed.

Program Binary

Static Variables

String Literals

Img Src: OpenGenus

Stack

Last In First Out

For data that is declared as variables within a function. The location of this memory never changes for the duration of a function call; because of this compilers can optimize code so stack data is very fast to access.

Function Arguments

Local Variables

Size is known at Compile time

let a:i32 = 100;

Rust knows this will take 32 bits of memory.

So the variable is stored in Stack Memory.

Automatic cleanup, when the function returns.

Example: Courtesy mit.edu

fn foo() {
    let y = 5;
    let z = 100;
}

fn main() {
    let x = 42;

    foo();
}
AddressNameValue
0x42

After calling the function foo()

AddressNameValue
2z100
1y5
0x42

Note: Memory address is taking into account DATA TYPE SIZE. It's just a representation.

After foo() gets executed, control transfers to main, and the values are deallocated automatically.

AddressNameValue
0x42

stateDiagram-v2
    [*] --> Main
    state Main {
        [*] --> PushMainFrame
        PushMainFrame: Push frame for main()
        PushMainFrame --> AllocateX: x = 42
        AllocateX --> CallFoo: Call foo()
        CallFoo --> PushFooFrame: Push frame for foo()
        state PushFooFrame {
            [*] --> AllocateY: y = 5
            AllocateY --> AllocateZ: z = 100
        }
        PushFooFrame --> PopFooFrame: Pop frame for foo()
        PopFooFrame --> ReturnFoo: foo() returns
        ReturnFoo --> PopMainFrame: Pop frame for main()
        PopMainFrame --> [*]
    }
    Main --> [*]

Copy Trait

    fn main() {
    // i32 is a simple type and are normally stored on the stack.
    // copy trait

    let x = 42;
    let y = x; 

    // The value bound to x is Copy, so no error will be raised.
    println!("{:?}", (x, y));

    // The value bound to x is Copy, so no error will be raised.
    println!("{:p},{:p}", &x, &y);
}

Heap

For data that is created while the application is running. Data in this region may be added, moved, removed, resized, etc. Because of its dynamic nature, it's generally considered slower to use, but it allows for much more creative usage of memory. When data is added to this region, we call it an allocation. When data is removed from this section, we call it deallocation.

Example: Vector, String

fn main(){
    let s1=String::from("hello");
    println!("{}",s1)
}
// Move Trait (Heap)

fn main() {
    let mut name = String::from("Hello World");
    println!("Memory address of name: {},{:p} \n", name,&name);

    //moving

    let name1 = name;
    println!("Memory address of name1: {},{:p} \n", name1,&name1);
    
    //println!("Memory address of name: {},{:p} \n", name,&name);

    // Setting up another Value for the variable name

    name = String::from("Dear World");
    println!("Memory address of name: {},{:p}\n", name,&name);
}

Example: pdf (stack) - printed book (heap)

// Copy Trait (Stack - because of using String Literal)

fn main() {
    let name = "Hello World";
    println!("Memory address of name: {},{:p} \n", name,&name);

    //Copying

    let name1 = name;
    println!("Memory address of name1: {},{:p} \n", name1,&name1);
    
    println!("Memory address of name: {},{:p} \n", name,&name);
}