Memory allocation in Rust

While learning “15.3. Running Code on Cleanup with the Drop Trait” of the Rust official book, I just wondered the memory layout of Rust program.

TL;DR: Currently the default global allocator is unspecified. Libraries, however, like cdylibs and staticlibs are guaranteed to use the System by default.

Struct std::alloc::System:

The default memory allocator provided by the operating system. This is based on malloc on Unix platforms and HeapAlloc on Windows, plus related functions.

Just in case, here is the dirty code:

use std::ops::Deref;

struct MyBox<T>(T);

impl<T> MyBox<T> {
    fn new(x: T) -> MyBox<T> {
        MyBox(x)
    }
}

impl<T> Deref for MyBox<T> {
    type Target = T;

    fn deref(&self) -> &T {
        &self.0
    }
}


fn hello(name: &str) {
    let in_hello: usize = 61;
    println!("&in_hello:    {:p}", &in_hello);
    println!("inside name:  {:p}", name);
    println!("inside &name: {:p}", &name);
}

fn main() {
    // Stack variables
    let stack_int_1: usize = 41;
    let stack_int_2: usize = 42;
    let stack_int_3: usize = 43;
    let my_box = MyBox::new(String::from("Rust"));

    // Heap variables
    let string = String::from("bar");
    //let string_slice: str = string[1..3]; // the size for values of type `str` cannot be known at compilation time
    let heap_box_1 = Box::new(51);
    let heap_box_2 = Box::new(String::from("EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE"));
    let heap_box_3 = Box::new(53);
    let vec1: Vec<i64> = vec![0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9];
    let vec2: Vec<u8> = vec![0,1,2,3,4,5,6,7,8,9];
    let heap_box_4 = Box::new(54);

    // Data segments
    let static_str: &'static str = "foo";
    let x: &[u8] = &[b'a', b'b', b'c'];
    let stack_str: &str = std::str::from_utf8(x).unwrap();
    let string_literal = "string literal";
    let string_data: &str = "in data";

    println!("\n==========================");
    println!("Supposed to be in stack");
    println!("==========================");
    println!("&stack_int_1:     {:p}", &stack_int_1);
    println!("&stack_int_2:     {:p}", &stack_int_2);
    println!("&stack_int_3:     {:p}", &stack_int_3);
    println!("my_box:           {:p}", &my_box);
    println!("\nThe addresses are growing in defined order.");
    println!("But, the order of variables inside a function stack frame is");
    println!("irrelevant to growing direction of the stack.");

    println!("\n==========================");
    println!("Supposed to be in heap");
    println!("==========================");
    println!("&(*string)        {:p}", &(*string));
    println!("heap_box_1:       {:p}", heap_box_1);
    println!("heap_box_2:       {:p}", heap_box_2);
    println!("heap_box_3:       {:p}", heap_box_3);
    println!("&(*vec1):         {:p}", &(*vec1));
    println!("&(*vec2):         {:p}", &(*vec2));
    println!("heap_box_4:       {:p}", heap_box_4);
    println!("\nThe addresses are growing in defined order.");

    println!("\n==========================");
    println!("Supposed to be in data");
    println!("==========================");
    println!("static_str: &'static str  {:p}", static_str);
    println!("x: &[u8]:                 {:p}", x);
    println!("stack_str: &str           {:p}", stack_str);
    println!("string_literal = '...':   {:p}", string_literal);
    println!("string_data: &str         {:p}", string_data);
    println!("\nThe addresses are growing in defined order.");

    println!("\n\n");

    let a = hello;             
    println!("function a:   {:p}", &a);
    a(&my_box);
}

And, this is the sample result:

==========================
Supposed to be in stack
==========================
&stack_int_1:     0x7fffcc413e90
&stack_int_2:     0x7fffcc413e98
&stack_int_3:     0x7fffcc413ea0
my_box:           0x7fffcc413ea8

The addresses are growing in defined order.
But, the order of variables inside a function stack frame is
irrelevant to growing direction of the stack.

==========================
Supposed to be in heap
==========================
&(*string)        0x5623955b8bc0
heap_box_1:       0x5623955b8be0
heap_box_2:       0x5623955b8c50
heap_box_3:       0x5623955b8c70
&(*vec1):         0x5623955b8c90
&(*vec2):         0x5623955b8d40
heap_box_4:       0x5623955b8d60

The addresses are growing in defined order.

==========================
Supposed to be in data
==========================
static_str: &'static str  0x56239496316e
x: &[u8]:                 0x562394963171
stack_str: &str           0x562394963171
string_literal = '...':   0x56239496317f
string_data: &str         0x56239496318d

The addresses are growing in defined order.



function a:   0x7fffcc414760
&in_hello:    0x7fffcc413b80
inside name:  0x5623955b8ba0
inside &name: 0x7fffcc413b70

disclaimer: I ran the code on Ubuntu 20.04.

  • {:?} points to a virtual memory address.

  • You can see stack is growing from high to low memory address (&in_hello is lower than &stack_int_1).

  • The default memory allocation

  • If you want to use system memory allocator explicitly, add the lines:

    use std::alloc::System;
    
    #[global_allocator]
    static GLOBAL: System = System;
    

To be updated: &&str is an address of stack