On this page

Ownership Model

Rust’s ownership model consists of only three rules: each value has a single owner, values are dropped when the owner goes out of scope, and assignment and passing arguments transfer ownership (move). Copy is an exception to move (both variables are usable after a bitwise copy), Clone is an explicit deep copy, and partial move allows you to move out specific fields from a struct. These three rules are the foundation of how Rust guarantees memory safety without a garbage collector.

Why Rust Needs Ownership

C’s manual malloc/free and C++’s RAII both rely on programmer discipline—forgotten releases, double frees, and use-after-free errors are completely invisible at compile time. GC languages manage memory automatically but introduce runtime overhead and stop-the-world latency. Rust’s answer: guarantee memory safety at compile time through ownership rules, with zero runtime overhead.

The core three rules:

  1. Each value in Rust has exactly one owner
  2. When the owner goes out of scope, the value is dropped
  3. Assignment/passing arguments/return values transfer (move) ownership, unless the type implements Copy

Move Semantics

let s1 = String::from("hello");
let s2 = s1;                         // s1's ownership is moved to s2
// println!("{}", s1);              // ERROR: s1 has been moved, cannot be used anymore

String stores three words (pointer, length, capacity) on the stack, with the actual data on the heap. let s2 = s1 performs a shallow copy (copying the 3 words on the stack), then marks s1 as invalid—no double free occurs because only s2 will call drop to free the heap memory when it goes out of scope.

The pseudo-code actually generated by the compiler:

s1 = String { ptr → heap[5 bytes "hello"], len: 5, cap: 5 }
s2 = String { ptr → same heap, len: 5, cap: 5 }   // move: copy stack fields
s1 is marked as "moved out" → drop(s1) is skipped by the compiler

Copy vs Clone

let x = 42;
let y = x;                           // x is still usable — i32 implements Copy
println!("{} {}", x, y);             // OK

The Copy trait is a marker automatically recognized by the compiler—types that implement Copy perform a bitwise copy rather than a move during assignment. The condition is: all fields of the type must implement Copy. i32, bool, char, &T implement Copy; String, Vec<T>, Box<T> do not—because they have heap data.

Clone is an explicit deep copy: .clone() is called by the programmer and can allocate new heap memory. String implements Clone but not Copy—an explicit .clone() produces a new String without transferring ownership.

let s1 = String::from("hello");
let s2 = s1.clone();                 // Deep copy: new heap allocation
println!("{} {}", s1, s2);           // Two independent Strings, both valid

Where Moves Occur

// 1. Assignment
let s2 = s1;                         // move

// 2. Passing arguments
fn take(s: String) {}                // s takes ownership
take(s1);                            // s1 moves into s → s1 becomes invalid

// 3. Return values
fn give() -> String { String::from("hi") }  // Ownership transfers to caller with the return value

// 4. Pattern matching
let (a, b) = (vec![1], vec![2]);     // Both Vecs move into a and b respectively

// 5. Closure capture
let v = vec![1, 2, 3];
let closure = || { println!("{:?}", v); };  // Closure captures v by move (because Vec doesn't implement Copy)
// v can no longer be used externally!

Stack Data vs Heap Data

The key to understanding move and copy: bitwise copying of stack data is safe—ownership transfer only requires copying stack contents. Transferring ownership of heap data also only requires copying the pointer on the stack; the heap data itself is not copied.

let s1 = String::from("hello");      // stack: [ptr|len|cap], heap: "hello"
let s2 = s1;                         // stack copy: [same ptr|same len|same cap]
                                     // s1 marked as moved → drop(s1) is not called
// End of scope: drop(s2) → frees "hello" on heap

Conditions for Copy

Which types automatically implement Copy?

  • All fields implement Copy
  • Does not implement Drop (if it has Drop, it means there are resources that need releasing—cannot be a simple bitwise copy)
#[derive(Copy, Clone)]               // Manually derive Copy (also requires Clone)
struct Point { x: i32, y: i32 }      // All fields are Copy → Point is also Copy

struct MyString(String);             // String is not Copy → MyString is not Copy
// Cannot derive Copy for MyString!

Partial Move

let p = (String::from("hello"), 42);
let (s, n) = p;                      // s takes ownership of the String, n copies 42
// println!("{:?}", p);             // ERROR: p.0 has been moved
println!("{}", p.1);                 // OK: p.1 is i32, copied, still in p

Drop Order

When a scope ends, variables are dropped in reverse order of declaration:

{
    let a = String::from("first");   // Declared first
    let b = String::from("second");  // Declared later
}                                    // drop(b) → drop(a)

This is important for safety—later variables may reference earlier ones; dropping later variables first ensures references remain valid.

References

  • Rust Book: Chapter 4.1 — What is Ownership
  • Rust Reference: destructors, move semantics
  • RFC: RFC 213 (default Move semantics)

Keywords: ownership, move, Copy, Clone, stack, heap, drop, partial move, RAII