Memory Management in Rust: Why No Garbage Collector and How It Works

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

What is a Garbage Collector and Why Did Rust Abandon It?

\n\n

If you come from the world of Python, Java, or C#, you are used to memory being managed by a Garbage Collector (GC). It is an invisible "cleaner" that periodically checks which objects are no longer in use and frees the memory they occupy. Convenient? Yes. But there are nuances:

\n\n
    \n
  • Unpredictable pauses. The GC can start at the most inopportune moment — when your code is processing an important request or rendering a frame in a game.
  • \n
  • Memory overhead. The GC often requires additional memory for its operation.
  • \n
  • Complexity in real-time systems. In operating systems, drivers, or embedded devices, GC pauses are unacceptable.
  • \n
\n\n

Rust offers a radically different approach. Instead of a garbage collector, it uses an ownership system that works at compile time. The compiler itself decides when memory should be freed and does so without any runtime overhead. Let's figure out how it works.

\n\n

The Ownership System — The Foundation of Memory Management

\n\n

In Rust, every value has exactly one owner — the variable to which this value belongs. When the owner goes out of scope, the memory is automatically freed. No garbage collector, no reference counting — only the compiler and strict rules.

\n\n

Ownership Rules in Rust

\n\n

Here are the three main rules that the compiler checks at compile time:

\n\n
    \n
  • Every value in Rust has one owner.
  • \n
  • When the owner goes out of scope, the value is dropped.
  • \n
  • The value can be transferred to another owner (move) or borrowed (borrow).
  • \n
\n\n
fn main() {\n    let s1 = String::from("Hello"); // s1 — owner of the string\n    let s2 = s1;                     // Ownership MOVES to s2 (move)\n    // println!("{}", s1);           // Error! s1 no longer owns the string\n    println!("{}", s2);             // Ok: s2 — new owner\n} // Here both s1 and s2 go out of scope,\n  // but memory is freed only once — for s2
\n\n

In Python or Java, you would simply create another reference to the object, and the GC would deal with it later. In Rust — no surprises: the compiler guarantees that memory will be freed exactly once.

\n\n

Borrowing — How to Use Data Without Transferring Ownership

\n\n

Often we need to temporarily "borrow" access to data without taking it away from the owner. For this, Rust has references. They come in two types:

\n\n
    \n
  • Immutable references (&T): you can read data but cannot modify it.
  • \n
  • Mutable references (&mut T): you can read and modify, but only if there are no other active references.
  • \n
\n\n
fn main() {\n    let mut text = String::from("Rust");\n    \n    let r1 = &text;     // immutable reference\n    let r2 = &text;     // another immutable — allowed\n    println!("{} {}", r1, r2);\n    \n    let r3 = &mut text; // mutable reference — works because r1 and r2 are no longer used\n    r3.push_str(" rocks!");\n    println!("{}", r3);\n    \n    // But this is not allowed:\n    // let r1 = &text;\n    // let r3 = &mut text; // Error! Cannot have mutable and immutable references simultaneously\n}
\n\n

The Rust compiler is not just a static analyzer; it is a strict but fair teacher. It won't let you shoot yourself in the foot. If you try to create a mutable reference while immutable ones exist, you will get a compilation error. This is the work of the borrow checker — the mechanism that replaces

Blogs

Book Recommendations