The stack and the heap
Because memory allocation is very important in Rust, we must have a good mental picture of what is going on. A program's memory is divided into the stack and heap memory parts; to get more background on these concepts go tohttps://stackoverflow.com/questions/79923/what-and-where-are-the-stack-and-heap.
Primitive values as numbers (like 32 in the figure), characters, or true or false values are stored on the stack, but the value of more complex objects that could grow in size are stored in heap memory. Heap values are referenced by a variable on the stack, which contains the memory address of the object on the heap.
While the stack has a limited size, the size of the heap can grow as much as the space needed.
Suppose we run the following program and try to visualize the program's memory:
// see Chapter 2/code/references.rs let health = 32; let mut game = "Space Invaders";
Values are stored in memory and so have memory addresses. The variable health contains an integer value 32 which is stored in the stack on location 0x23fba4, while the variable game contains a string, which is stored in the heap starting on location 0x23fb90 (these were the addresses when I executed the program, they will be different when you run the program).
The variables to which the values are bound are pointers or references to these values, they point to them. The variable game is a reference to "Space Invaders". The address of a value is given by the & operator. So, the &health pointer is the address where value 32 is stored, and the &game pointer is the address where value Space Invaders is stored.
We can print these addresses by using the format string {:p} for pointers, like this:
println!("address of health-value: {:p}", &health); // prints 0x23fba4 println!("address of game-value: {:p}", &game); // prints 0x23fb90 println!("game-value: {}", game); // prints "Space Invaders"
Now, we have the following situation in memory (memory addresses will be different at each execution):
We can make an alias, which is another reference that points to the same place in memory, like this:
let game2 = &game; println!("{:p}", game2); // prints 0x23fb90
To get the value being referred to rather than the reference game2 itself, dereference it with the asterisk * operator, like this:
println!("{}", *game2); // prints "Space Invaders"
The println! macro is clever, so println!("{}", game2);will also print out the same value, as the following statement does:
println!("game: {}", &game);
The story above is a bit simplified, because Rust will allocate values that do not change in size as much as possible on the stack, but it is meant to give you a better idea of what a reference to a value means.
We know already that a let binding is immutable, so the value cannot be changed as health = 33;.
This gives an error, as follows:
error: re-assignment of immutable variable `health`
If the variable y is declared with let y = &health; then the reference *y is the value 32. Reference variables can also be given a type like let x: &i64; and such references can be passed around in code. After this let binding x does not really point yet to a value, it does not contain a memory address. In Rust, there is no way to create a null pointer as you can in other languages, trying to assign a nil or null or even unit value () to x results in an error. This alone saves Rust programmers from countless bugs. Furthermore, trying to use the variable x in an expression, for example, the statement:
println!("{:?}", x);
This results with an error, as follows:
error: use of possibly uninitialized variable: `x`
A mutable reference (indicated as the &mut pointer) to an immutable variable is forbidden, otherwise the immutable variable could be changed through its mutable reference:
let tricks = 10; let reftricks = &mut tricks;
This gives an error, as follows:
cannot borrow immutable local variable `tricks` as mutable
A reference to a mutable variable score can either be immutable or mutable, like the score2 and score3 variables respectively in the example below:
let mut score = 0; let score2 = &score;
But you cannot change the value of score through an immutable reference to the score2 variable, as this gives an error, as follows:
*score2 = 5;
This gives the an error: cannot assign to immutable borrowed content *score2
The value of the variable score can only be changed through a mutable reference like score3:
let mut score = 0; let score3 = &mut score; *score3 = 5;
For reasons we will see later, you can only make one mutable reference to a mutable variable:
let score4 = &mut score;
If you do this, an error is thrown as follows:
error: cannot borrow `score` as mutable more than once at a time
Here, we touch at the heart of Rust's memory safety system, borrowing a variable is one of its key concepts. We will explore this in more detail in the Chapter 7, Ensuring Memory Safety and Pointers.
The heap is a much larger memory part than the stack, so it is important that memory locations are freed as soon as they are no longer needed. Every variable in Rust has a certain lifetime, which says how long the variable lives in the program's memory. The Rust compiler sees when a variable's lifetime has come to an end (or in other words, the variable goes out of scope), and inserts code at compilation time to free its memory when executing that code. This behavior is unique to Rust and is not done in other commonly used languages.
Stack values can be boxed, that is, allocated in the heap, by creating a Box around them, as is the case for the value of x in:
let x = Box::new(5i32);
The object Box references a value on the heap. We'll also look at it more closely in the section Boxes in Chapter 7, Ensuring Memory Safety and Pointers.