Skip to content

Ownership and Borrowing in Rust: A Checklist That Explains the Rules

Posted on:December 21, 2025 at 07:06 PM

Rust’s ownership and borrowing system has a set of strict rules to memorize, but there’s an easier way to understand them.

Rust addresses three common problems when you work with heap data, such as String, Vectors (Vec<T>), or any value that can change size or has a dynamic size

  1. Who is allowed to use this variable?
  2. How many copies of it exist?
  3. When can it be safely freed or released?

By understanding these questions, Rust’s ownership and borrowing rules start to make sense.

The 3-Question Checklist (How Rust Decides “Move vs Borrow vs Clone”)

When you want to use heap data in a different variable place passing it to a function, storing it in a struct, or returning it—you have three choices:

1. Should the other variable become the owner? (Move)

If yes, the ownership transfers. You can’t use the original variable after that. Move applies to heap types (String, Vec)

fn main() {
    let a = String::from("hello");
    let b = a;              // move: b now owns the String
    // println!("{a}");     // error: a was moved
    println!("{b}");
}

2. Do I need a separate copy? (Clone vs Copy)

For Heap Data (Clone):

If you need an independent copy of the heap data (like String), use .clone(). But ask yourself, do you really need two variables to manage data?

fn main() {
    let a = String::from("hello");
    let b = a.clone();      // deep copy of heap data

    println!("{a}");
    println!("{b}");
}

For Primitives (Copy):

Primitive types (like i32, bool) are different. They implement the Copy trait, meaning they are copied on assignment.

fn main() {
    let x: i32 = 10;
    let y = x;              // Copy: x is copied to y
    println!("{x}");        // still usable
    println!("{y}");
}

3. Can I just borrow it? (Borrow)

If you want to use the value without taking ownership, borrow it with &.

fn print_len(s: &String) {
    println!("len = {}", s.len());
}

fn main() {
    let a = String::from("hello");

    print_len(&a);          // borrow (read-only)
    println!("{a}");        // still valid: ownership never moved
}

How long does this access last?

This is why Rust has lifetimes and has the rule against dangling references: references can’t last longer than the data they refer to.

What Borrowing Means

A reference: “I want to use that value, but I do NOT want to own it.”

Rust has two kinds:

These two types of references exist because reading and writing have different safety rules.

The Two Borrowing Rules (And Why They Exist)

Rust follows two main rules to keep heap data safe.

Rule 1: Many readers OR one writer (but not both)

At any given time, you can have either:

Why Rust does this

If Rust allowed a writer while readers exist:

This rule helps prevent many bugs that show up in other programming languages.

Many readers is ok

// this is fine, use & to read s through r1 and r2 
let s = String::from("hello");
let r1 = &s;
let r2 = &s;
println!("{r1} {r2}");

One writer is ok

let mut s = String::from("hello");
let w = &mut s;
w.push('!');

Readers + writer at the same time is not ok

let mut s = String::from("hello");
let r = &s;
let w = &mut s;   // Error: can't borrow 's' as mutable
// because 'r' borrows it as immutable.
println!("{r}");

“At the same time” means “overlapping lifetimes”

Rust tracks if borrows overlap, not just if they happen in the same function, so this is valid:

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

let r = &s;
println!("{r}");  // r is used here, then it's done

let w = &mut s;   // ok now, no overlap
w.push('!');

Rust detects when r is no longer used, which allows a mutable borrow.

Rule 2: References have to be valid (no dangling references)

A dangling reference occurs when a reference points to memory that has been freed or moved. Rust prevents this at compile time.

The following is not allowed: returning a reference to a local variable that will be dropped.

fn bad() -> &String {
    let s = String::from("hi");
    &s            // s is dropped when function ends
}

The Rust way: return the owned value instead (move ownership out):

fn good() -> String {
    let s = String::from("hi");
    s
}

Another common “dangling-ish” situation: Vec reallocation. A Vec might move its elements when it grows. If you hold a reference into the vector, then try to push, Rust blocks it:

let mut v = vec![1,2,3];
let r = &v[0];
v.push(4);        // can't mutate v while r exists
println!("{r}");

Rust is saying: “If the vector moves, your reference could point to the wrong place, so I will not allow that.”

How the Rules Answer the 3 Questions Automatically

Now connect it back to the original checklist:

Who can use it?

Borrowing allows others to use a value through & or &mut, but only under strict rules.

How many copies exist?

Borrowing enables shared access without copying. To create a copy, call clone().

When can it be freed?

“References have to be valid” means data can’t be freed or moved while it is still borrowed. Lifetimes ensure this rule is enforced.

Quick Guide: When to use * (Dereferencing)

  1. Read through a reference: &T -> T

    If you want to return the value (copy it out), you deref:

    fn read(x: &i32) -> i32 {
        *x
    }
  2. Modify through a mutable reference: &mut T

    If you want to change the caller’s variable, you need &mut and deref to write:

    fn add_one(x: &mut i32) {
        *x += 1;
    }
  3. Structs and Methods (Auto-deref)

    Rust often auto derefs for you.

    // These are equivalent:
    a.balance += amount;        //  auto deref
    (*a).balance += amount;     // explicit deref

    Rule of Thumb: Use & / &mut to borrow. Use * only when you need to read/write the value behind a primitive reference.

The Best Intuition

&T means multiple people can view the document. &mut T means one person can edit the document, and no one else may view it during that time. Reference means that you can’t discard the document while someone still has it open. That’s Rust ownership and borrowing.

Rust provides safe sharing, controlled mutation, and automatic cleanup without garbage collection.