alloc and dealloc

Compared to the stack, the heap is more versatile but more work to manage.

On the stack, all bytes from stack_length onward are available for use. All the bytes before it are reserved for use in functions that are currently executing. That's all there is to it.

In contrast, heap memory is decoupled from function execution. Here's how it works:

Manual memory management

In C, memory management functions like alloc and dealloc must be called explicitly by the programmer. Calling alloc is relatively straightforward, because it happens right when you actually need the bytes. However, figuring out when to call dealloc is notoriously error-prone.

Let's go back to our example from earlier, and add a couple of println! calls.

fn get_years() -> Vec<i32> {
    return vec![1995, 2000, 2005, 2010, 2015];
}

let years = get_years();

println!("The highest year is {}", find_highest_year(years));
println!("All done!");

If we were doing manual memory management, get_years would actually call alloc to reserve enough space for five i32 values on the heap. It would then return a VecMetadata struct whose memory_index_of_first_element value would be an index into the heap_bytes array. (We could have called it heap_bytes_index_of_first_element to be extra clear about this.)

However, when should dealloc be called?

One approach would be to call it at the very end of the program. Using that as a general strategy - deallocating everything at the very end of the program - is straightforward, but means that while the program is running, its heap memory usage only ever grows and never decreases. This is essentially the same thing as leaking all heap memory on purpose, which makes it about the same as never bothering to call dealloc at all!

Another approach would be to dealloc the heap memory as soon as it's no longer in use. In this example, that would be right between the two println! calls. This is optimal from a memory usage perspective; we are using the memory only as long as we need to.

Potential Mistakes

Let's say we make a mistake, though. What if we dealloc the memory before either println! call? That means the find_highest_year function will be reading heap memory that has potentially been overwritten with other data...and that other data could be anything by now! Instead of years, we could be looking at bytes representing recipes in a cookbook.

This is known as a "use-after-free" bug, and it happens when memory is accessed after it has been "freed" (deallocated). These can be extremely painful to debug, because the symptoms are that sometimes some of your data is random gibberish instead of what you expect.

Another potential mistake is that we could call dealloc twice on the same memory index. This is called a "double free," and it can lead to the same symptoms as "use-after-free" bugs. This is because once memory has been freed, the global allocation bookkeeping system may hand that "unused" memory index out to a later alloc request. If we then call dealloc a second time on that memory index, it will be deallocating memory that is still in use (by the newer allocation), which is a use-after-free!