Why Rust Has No Garbage Collector and What It Uses Instead
Rust frees memory at compile-time-determined points using ownership and Drop, not a runtime collector. See what that buys in predictability and footprint, what it costs in design effort, and where reference counting fills the gap.

Garbage collection is the standard answer to "who frees this memory?" in managed languages: a runtime periodically finds what is no longer reachable and reclaims it. Rust gives a different answer, ownership, which is decided when the program is compiled. The effect is memory safety with no collector, and the consequences show up in latency, footprint and how you design data. This post explains the mechanism with a small program. It compiles on Rust 1.90 with edition 2024.
Deterministic cleanup
struct Noisy(&'static str);
impl Drop for Noisy {
fn drop(&mut self) {
println!("drop {}", self.0);
}
}
fn main() {
let _a = Noisy("a");
{
let _b = Noisy("b");
println!("inner scope");
}
let c = Noisy("c");
drop(c);
println!("end of main");
}inner scope
drop b
drop c
end of main
drop ab is dropped at the closing brace of its scope, c at the explicit drop, and a at the end of main, in reverse order of declaration. The compiler decides these points; nothing is traced at run time. For a Vec<String> the drop frees each string and then the buffer, so a whole structure is released at one predictable moment (ownership explained).
What a collector costs, and what Rust avoids
| Concern | Tracing GC | Rust ownership |
|---|---|---|
| When memory is freed | At some later collection | At scope exit, deterministic |
| Pauses | Possible, tuned by collector design | None from memory management |
| Runtime and metadata | Collector, write barriers, headers | None; a plain allocator |
| Non-memory resources | Need finalizers or explicit close | Same mechanism: Drop closes files and unlocks mutexes |
| Programmer effort | Low for ownership, higher for tuning | Higher for ownership design, low for tuning |
Modern collectors are sophisticated, and pause times in tuned systems can be very small; the claim is not that GC is bad. The claim is that a language without one can run in places where a collector cannot: kernels, microcontrollers, audio callbacks (real-time audio) and programs that embed into hosts written in other languages without a second runtime.
Drop is not only for memory
use std::sync::Mutex;
fn main() {
let m = Mutex::new(vec![1, 2, 3]);
{
let mut guard = m.lock().unwrap();
guard.push(4);
} // guard dropped here: lock released
println!("{:?}", m.lock().unwrap());
}[1, 2, 3, 4]The same rule that frees a buffer unlocks the mutex, closes a file and finishes a transaction. This pattern, called RAII, is why Rust code rarely has explicit close calls, and why forgetting to release a lock is hard to do by accident.
Shared ownership when you need it
use std::rc::Rc;
fn main() {
let shared = Rc::new(String::from("config"));
let a = Rc::clone(&shared);
println!("{} owners", Rc::strong_count(&shared));
drop(a);
println!("{} owner", Rc::strong_count(&shared));
}2 owners
1 ownerWhen several parts of a program must own a value, Rc (single-threaded) and Arc (thread-safe) count references and free the value when the count reaches zero. It is deliberate, visible in the type, and local: only values you wrap pay for it (Box, Rc and Arc compared). Unlike a tracing collector it cannot reclaim cycles, which is why Weak exists.
Patterns that replace "just use a GC"
- Arenas: allocate many objects with one lifetime in a
bumpaloortyped-arenaregion and free them all at once; ideal for compilers and parsers. - Indices into a
Vec: graphs and trees asVec<Node>with integer ids avoid pointers and lifetimes entirely (data-oriented design). - Cloning: sometimes the right answer is a copy, especially for small data.
Honest limits
The borrow checker cost is real: some designs that are natural with a collector (cyclic object graphs, observer patterns with back-references) need rethinking or Rc<RefCell<T>>. Memory can still leak in safe Rust, through cycles or mem::forget (memory leaks in safe code). And allocation itself, done by the system allocator, has costs and fragmentation you may need to tune (allocators compared). Rust removes the collector, not the need to think about memory.


