Why Doesn't Rust Need a Garbage Collector, Yet Is Still Safe?
\n\nWhen developers accustomed to Python, Java, or C# first hear about Rust, they are often frightened by the absence of a familiar garbage collector (GC). "How can that be?" they think. "Without GC, we'll go back to the era of manual memory management, leaks, and segmentation faults?"
\n\nSpoiler: no, we won't. Rust offers a third path — the ownership system, which guarantees memory safety at compile time. No runtime, no GC, no dangling pointers. Only clear rules and a compiler that won't let you shoot yourself in the foot.
\n\nIn this article, we'll break down how memory works in Rust, what RAII, ownership, borrowing, and lifetimes are, and why all of this makes Rust one of the most reliable systems programming languages.
\n\nHow Memory Works in Rust: Stack and Heap
\n\nAs in any low-level language, memory in Rust is divided into two main regions:
\n\n- \n
- Stack — fast, limited-size memory operating on the LIFO principle. Local variables, function arguments, and return addresses are stored here. \n
- Heap — slower but more flexible memory, used for data whose size is unknown at compile time (e.g.,
String,Vec,Box). \n
In C++, you decide when to allocate and free heap memory (via new/delete). In Java or Python, a garbage collector does it for you, periodically stopping the program. Rust, however, uses an ownership system that automatically frees memory as soon as a variable goes out of scope.
Example: Basic Memory Allocation
\n\nfn main() {\n let s = String::from("Hello, world!"); // heap memory allocation\n println!("{}", s);\n} // here s goes out of scope, memory is freed\n\nNotice: no free() or delete. At the end of the block, the compiler implicitly inserts a destructor call — drop(). This is called RAII (Resource Acquisition Is Initialization) — a resource is acquired upon initialization and released when the object is destroyed.
Ownership: The Main Rule of Rust
\n\nThe ownership system in Rust is built on three simple rules:
\n\n- \n
- Each value in Rust has exactly one owner. \n
- When the owner goes out of scope, the value is dropped. \n
- Ownership can be transferred (move) or temporarily lent (borrow). \n
Sounds boring? Let's see how this works in practice.
\n\nMove
\n\nfn main() {\n let s1 = String::from("hello");\n let s2 = s1; // s1 is moved to s2\n // println!("{}", s1); // Error! s1 no longer owns the data\n println!("{}", s2); // OK\n}\n\nIn C++, such an assignment would result in two pointers to the same memory — and a double free. In Rust, the compiler simply forbids using s1 after the move. No leaks, no double deletions.
Copy
\n\nSimple types, such as integers, booleans, and characters, implement the Copy trait. For them, assignment creates a full copy:
fn main() {\n let x = 42;\n let y = x;\n println!("x = {}, y = {}", x, y); // Both are accessible!\n}\n\nWhy is that? Because i32 is stored on the stack, and copying it is just copying a few bytes. Cheap and safe.
Borrowing and References
\n\nWhat if you need to temporarily grant access to data without transferring ownership? Use references. Rust allows you to borrow a value via a reference.
\n\nfn main() {\n let s = String::from(