What does Rust have instead of a garbage collector?

MemoryRustMemory Management

Memory Problem Overview


I understand Rust doesn't have a garbage collector and am wondering how memory is freed up when a binding goes out of scope.

So in this example, I understand that Rust reclaims the memory allocated to 'a' when it goes out of scope.

{
    let a = 4
}

The problem I am having with this, is firstly how this happens, and secondly isn't this a sort of garbage collection? How does it differ from 'typical' garbage collection?

Memory Solutions


Solution 1 - Memory

Garbage collection is typically used periodically or on demand, like if the heap is close to full or above some threshold. It then looks for unused variables and frees their memory, depending on the algorithm.

Rust would know when the variable gets out of scope or its lifetime ends at compile time and thus insert the corresponding LLVM/assembly instructions to free the memory.

Rust also allows some kind of garbage collection, like atomic reference counting though.

Solution 2 - Memory

The basic idea of managing resources (including memory) in a program, whatever the strategy, is that the resources tied to unreachable "objects" can be reclaimed. Beyond memory, those resources can be mutex locks, file handles, sockets, database connections...

Languages with a garbage collector periodically scan the memory (one way or another) to find unused objects, release the resources associated with them, and finally release the memory used by those objects.

Rust does not have a GC, how does it manage?

Rust has ownership. Using an affine type system, it tracks which variable is still holding onto an object and, when such a variable goes out of scope, calls its destructor. You can see the affine type system in effect pretty easily:

fn main() {
    let s: String = "Hello, World!".into();
    let t = s;
    println!("{}", s);
}

Yields:

<anon>:4:24: 4:25 error: use of moved value: `s` [E0382]
<anon>:4         println!("{}", s);

<anon>:3:13: 3:14 note: `s` moved here because it has type `collections::string::String`, which is moved by default
<anon>:3         let t = s;
                     ^

which perfectly illustrates that at any point in time, at the language level, the ownership is tracked.

This ownership works recursively: if you have a Vec<String> (i.e., a dynamic array of strings), then each String is owned by the Vec which itself is owned by a variable or another object, etc... thus, when a variable goes out of scope, it recursively frees up all resources it held, even indirectly. In the case of the Vec<String> this means:

  1. Releasing the memory buffer associated to each String
  2. Releasing the memory buffer associated to the Vec itself

Thus, thanks to the ownership tracking, the lifetime of ALL the program objects is strictly tied to one (or several) function variables, which will ultimately go out of scope (when the block they belong to ends).

Note: this is a bit optimistic, using reference counting (Rc or Arc) it is possible to form cycles of references and thus cause memory leaks, in which case the resources tied to the cycle might never be released.

Solution 3 - Memory

With a language where you must manually manage memory, the distinction between the stack and the heap becomes critical. Every time you call a function, enough space is allocated on the stack for all variables contained within the scope of that function. When the function returns, the stack frame associated with that function is "popped" off the stack, and the memory is freed for future use.

From a practical standpoint, this inadvertent memory cleaning is used as a means of automatic memory storage that will be cleared at the end of the function's scope.

There is more information available here: https://doc.rust-lang.org/book/the-stack-and-the-heap.html

Solution 4 - Memory

Some languages have reference counting, some have garbage collectors. Rust avoids both, instead, it allows only a single variable name or alias if you like to own a memory location at any point in time. You can move the ownership from one variable name to another, but you can’t have two variable names pointing to the same memory address (Except for shared Ownership. Rust provides the reference-counted pointer types Rc and Arc. Because sometimes it’s difficult to find every value a single owner that has the lifetime you need).

> Rust uses a relatively unique memory management approach that > incorporates the idea of memory “ownership”. Basically, Rust keeps > track of who can read and write to memory. It knows when the program > is using memory and immediately frees the memory once it is no longer > needed. It enforces memory rules at compile time, making it virtually > impossible to have runtime memory bugs. You do not need to manually > keep track of memory. The compiler takes care of it.

Discord recently switched from Go to Rust in one of its services just because garbage collector was causing latency. They explained very well why they did this and you will learn more about the garbage collector and rust memory system:

https://discord.com/blog/why-discord-is-switching-from-go-to-rust#:~:text=Discord%20is%20a%20product%20focused,and%20messages%20you%20have%20read.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionrixView Question on Stackoverflow
Solution 1 - MemoryAyonixView Answer on Stackoverflow
Solution 2 - MemoryMatthieu M.View Answer on Stackoverflow
Solution 3 - MemorySwissView Answer on Stackoverflow
Solution 4 - MemoryYilmazView Answer on Stackoverflow