Rust Where Values Live
A value lives whole on the stack or behind a handle with its data on the heap. Arrays and tuples sit whole.
An array keeps its items inside itself
An array has a fixed count of items. The whole array sits in one place:
let row = [10, 20, 30];
println!("the whole array = {} bytes", std::mem::size_of_val(&row));A tuple packs its parts into one value
The u8 and the u32 sit side by side in one value. Padding fills a small gap, so the pair takes 8 bytes, not 5:
let pair = (7u8, 1000u32);
println!("pair = {} bytes", std::mem::size_of_val(&pair));A String is a handle, however long the text
The handle stays on the stack. The letters live on the heap:
let s = String::from("hi");
println!("handle = {} bytes, text = {} letters", std::mem::size_of_val(&s), s.len());A text view is a handle that owns nothing
A &str points at text that lives somewhere else. It frees nothing when it goes away:
let view: &str = "hello";
println!("view handle = {} bytes, text = {} letters", std::mem::size_of_val(&view), view.len());Some values weigh nothing at all
The unit value and an empty array take no room. They carry facts for the compiler, not data:
println!("unit = {} bytes, empty array = {} bytes", std::mem::size_of_val(&()), std::mem::size_of_val(&[0u8; 0]));Example
use std::mem::size_of_val;
fn main() {
println!("i32 = {}", size_of_val(&7i32));
println!("bool = {}", size_of_val(&true));
println!("char = {}", size_of_val(&'x'));
println!("[i32; 3] = {}", size_of_val(&[1i32, 2, 3]));
println!("(i32, bool) = {}", size_of_val(&(7i32, true)));
println!("String handle = {}", size_of_val(&String::from("hi")));
println!("&str handle = {}", size_of_val(&"hi"));
}Key points
- Every value sits in one of two ways: whole, right where it is declared, or behind a handle
- Arrays and tuples hold their parts inside the value itself: move the value and the parts travel with it
- String, Vec and Box are handles.
- A small fixed part sits on the stack.
- The data sits on the heap
- A view such as &str is also a handle: it holds an address and a length, but it owns nothing
- Use std::mem::size_of_val(&value) to weigh a value in bytes
- The stack and the heap are the memory areas from Rust Stack & Heap.
In this example, the programme weighs one value of each kind. The plain values come first. An i32 weighs 4 bytes, a bool weighs 1, and a char weighs 4. The array weighs 12, because it holds three i32 values inside itself. The tuple weighs 8: the i32 takes 4, the bool takes 1, and padding fills the rest. Then come the handles. A String handle weighs 24 bytes, however long the text on the heap gets. A &str handle weighs 16: one address and one length. Nothing in this list hides a pointer except the two handles at the end.
Note
Many languages hide every object behind a pointer.
Rust keeps plain values whole and together, until you ask for a handle yourself.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
A String holds one letter. Another String holds a whole novel. What weighs the same in both?