Rust Ownership: Move vs Copy & No Garbage Collection

Max WellsMax WellsFounder of Rustify

TL;DR: Ownership is Rust's system for managing memory without a garbage collector. Every value has exactly one owner. When the owner goes out of scope, the value is dropped automatically. This eliminates memory leaks, dangling pointers, and data races at compile time, with zero runtime cost.


What Is Ownership in Rust?

Ownership is a set of compile-time rules that governs how Rust programs manage memory, replacing both garbage collection and manual memory management with a system that is both safe and fast.

Every other mainstream language picks one of two approaches: garbage collection (Java, Python, Go) pauses the program to reclaim unused memory; manual management (C, C++) gives the programmer full control but enables bugs. Rust does neither. Instead, the compiler tracks ownership and frees memory automatically when values go out of scope.

The result: memory safety with the performance of C.


What Are the Three Rules of Ownership?

Rust's ownership model is governed by exactly three rules that the compiler enforces at every build:

  1. Each value has exactly one owner, a variable that holds it
  2. There can only be one owner at a time, ownership can be moved but not shared
  3. When the owner goes out of scope, the value is dropped, memory is freed automatically
fn main() {
    let s1 = String::from("hello"); // s1 owns the String
    let s2 = s1;                    // ownership moves to s2; s1 is now invalid
 
    // println!("{s1}"); // ❌ compile error: s1 no longer owns the value
    println!("{s2}");   // ✅ s2 is the owner
} // s2 goes out of scope; String is dropped, memory freed

This is called a move. Unlike a copy, the original variable becomes invalid after a move.


What Is the Difference Between Move and Copy?

Types that are cheap to duplicate (integers, booleans, floats) implement the Copy trait and are copied instead of moved, the original remains valid.

fn main() {
    let x = 5;      // i32 implements Copy
    let y = x;      // x is copied, not moved
 
    println!("{x}"); // ✅ x is still valid
    println!("{y}"); // ✅ y has its own copy
}

Types that manage heap memory (String, Vec, Box) do not implement Copy, they move. If you need both variables to remain valid, use .clone():

let s1 = String::from("hello");
let s2 = s1.clone(); // deep copy; s1 and s2 are independent
 
println!("{s1}"); // ✅ still valid
println!("{s2}"); // ✅ also valid

How Does Ownership Work With Functions?

Passing a value to a function moves or copies it, the same rules that apply to variable assignment apply to function calls.

fn take_ownership(s: String) {
    println!("{s}");
} // s is dropped here
 
fn make_copy(n: i32) {
    println!("{n}");
} // n is dropped, but the caller's copy is unaffected
 
fn main() {
    let s = String::from("hello");
    take_ownership(s);    // s is moved into the function
    // println!("{s}");   // ❌ s is no longer valid here
 
    let x = 5;
    make_copy(x);         // x is copied
    println!("{x}");      // ✅ x is still valid
}

To avoid moving ownership into a function, use references instead, see the Borrow Checker glossary entry.


Why Does Rust Use Ownership Instead of Garbage Collection?

Ownership gives Rust deterministic, zero-cost memory management, memory is freed at a predictable point (scope exit) with no runtime pauses and no overhead.

ApproachRuntime costSafetyPredictability
Garbage collectionGC pauses, memory overheadSafeUnpredictable
Manual (malloc/free)ZeroUnsafe, leaks, UAF bugsManual
Rust ownershipZeroSafe, enforced by compilerDeterministic

This is why Rust is used for latency-sensitive systems: game engines, databases, network proxies, and OS kernels where GC pauses are unacceptable.


Frequently Asked Questions

Related but distinct. Ownership defines who is responsible for a value. The borrow checker enforces rules around lending references to values without transferring ownership. Together they form Rust's memory safety system.

Because Rust would not know which owner is responsible for freeing the memory, double-free errors are a common source of security vulnerabilities in C++. Single ownership eliminates this class of bug entirely.

When a value is dropped, Rust calls its Drop implementation (if any) and frees its memory. This happens automatically when the owning variable goes out of scope. You can also drop manually with drop(value).

Yes, but stack memory is freed automatically when a function returns regardless. Ownership rules matter most for heap-allocated values (String, Vec, Box) where the programmer would otherwise need to manage memory manually.


Sources


  • Borrow Checker: How Rust enforces safe references
  • Lifetime: How long references are valid
  • Box: Heap allocation in Rust
  • Reference: Ownership and borrowing meet through &T and &mut T
  • Copy/Clone: The traits that determine when values move, copy, or duplicate
  • Interior Mutability: Patterns that bend ownership rules without breaking Rust's guarantees
  • const-static: Global values and 'static data still obey Rust's ownership model
  • RefCell: RefCell is one of the core ownership escape hatches for single-threaded mutable access

Keep Reading

Ready to Land a $120k+ Rust Job in the US or Europe?