Ownership in Rust

Every value in Rust has exactly one owner at a time, and when the owner goes away, the value is automatically cleaned up.

Let’s build this step by step.


1️⃣ The three ownership rules

Rust enforces these at compile time:

  1. Each value has one owner
  2. There can only be one owner at a time
  3. When the owner goes out of scope, the value is dropped

That’s it—but these rules have powerful consequences.


2️⃣ What is an “owner”?

The owner is the variable responsible for freeing the memory.

let s = String::from("hello");
  • s owns the String
  • The heap memory for "hello" belongs to s

When s goes out of scope:

} // s is dropped here → memory freed

No free, no delete, no GC.


3️⃣ Move semantics (ownership transfer)

In Rust, assigning or passing a value often moves ownership.

let a = String::from("hi");
let b = a;

What happens?

  • Ownership moves from ab
  • a is now invalid
println!("{}", a); // ❌ compile-time error

Why this matters

In C++, both a and b could point to the same memory → double free or use-after-free risk.

Rust avoids this by making ownership exclusive.


4️⃣ Copy types vs move types

Some types are cheap and safe to copy:

let x = 5;
let y = x; // copy
println!("{}", x); // OK

These are types that implement Copy:

  • integers
  • floats
  • bool
  • small fixed-size structs with no heap data

Heap-owning types like String, Vec<T>, Box<T> are not Copy.


5️⃣ Borrowing: using without owning

What if you want to use a value without taking ownership?

Immutable borrow

fn len(s: &String) -> usize {
    s.len()
}

let s = String::from("hello");
let l = len(&s); // borrow
println!("{}", s); // still valid

Rules:

  • Many immutable borrows allowed
  • No mutation allowed while borrowed

Mutable borrow

fn push_x(s: &mut String) {
    s.push('x');
}

let mut s = String::from("hi");
push_x(&mut s);

Rules:

  • Only one mutable borrow at a time
  • No other borrows (mutable or immutable) while it exists

This prevents data races and aliasing bugs.


6️⃣ Why Rust forbids multiple mutable borrows

This code is illegal:

let mut s = String::from("hello");

let r1 = &mut s;
let r2 = &mut s; // ❌

Why?

  • Two mutable references could modify the same memory unpredictably
  • In multithreaded code, this would be a data race

Rust prevents this at compile time, even in single-threaded code.


7️⃣ Ownership + borrowing prevent dangling references

This is impossible in safe Rust:

let r;
{
    let s = String::from("hello");
    r = &s; // borrow
} // s dropped here
println!("{}", r); // ❌

Rust sees that r would outlive s and refuses to compile.

This is how Rust prevents dangling references.


8️⃣ Ownership across function boundaries

Taking ownership

fn consume(s: String) {
    println!("{}", s);
}

let s = String::from("hi");
consume(s);
println!("{}", s); // ❌ s moved

Borrowing instead

fn read(s: &String) {
    println!("{}", s);
}

let s = String::from("hi");
read(&s);
println!("{}", s); // OK

The function signature tells you exactly what happens to ownership.


9️⃣ Ownership enables safe concurrency

Because:

  • Only one owner
  • Mutable access is exclusive

Rust can guarantee:

No data races in safe Rust

Sharing across threads must be explicit:

  • Arc<T> for shared ownership
  • Mutex<T> / atomics for mutation

10️⃣ Mental model (simple & practical)

Think in questions:

  1. Who owns this value?
  2. Am I moving it or borrowing it?
  3. Is this borrow shared (&) or exclusive (&mut)?
  4. Does the owner live long enough?

If those answers are clear, Rust code usually “just works”.


One-sentence summary

Ownership is Rust’s compile-time system that ensures each piece of memory has exactly one responsible owner, enabling automatic cleanup, preventing use-after-free, data races, and double frees—without a garbage collector.

Leave a Reply

Your email address will not be published. Required fields are marked *