← Home ← Codex ← DEBT
Browse by Category
+ added · updated 7d
← Back to glossary

Rust Ownership and Borrowing

rust Beginner
debt(d1/e2/b5/t7)
d1 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'caught instantly' (d1), rustc itself blocks compilation with errors like 'borrow of moved value' and 'cannot borrow as mutable'; the borrow checker is the compiler so misuse never reaches runtime.

e2 Effort Remediation debt — work required to fix once spotted

Closest to 'simple parameterised fix' (e3), quick_fix is replacing a move with &value or &mut value — usually a one-to-few-line change, occasionally requiring small signature adjustments, so slightly above a pure one-line swap.

b5 Burden Structural debt — long-term weight of choosing wrong

Closest to 'persistent productivity tax' (b5), ownership applies across all contexts (web/cli/queue/library) and shapes how data flows through every function signature and lifetime, slowing many work streams until internalized.

t7 Trap Cognitive debt — how counter-intuitive correct behaviour is

Closest to 'serious trap' (t7), the misconception is that assignment copies as in most languages, but non-Copy types are moved and invalidate the original — directly contradicting how nearly every other mainstream language behaves.

About DEBT scoring →

Also Known As

ownership rules move semantics borrow checker references and borrowing

TL;DR

Ownership gives each value one owner and borrowing lends temporary access, letting Rust guarantee memory safety without a garbage collector.

Explanation

Ownership is the core rule that makes Rust memory-safe without a garbage collector. Every value has exactly one owning variable, and when that owner goes out of scope the value is dropped and its resources freed. There is no double-free and no use-after-free because the compiler tracks who owns what and when it ends. Assigning or passing a non-Copy value by value moves ownership: the original binding becomes invalid, so two variables can never both believe they own the same heap allocation.

Moves are why `let b = a;` for a `String` leaves `a` unusable - the heap buffer now belongs to `b`. Small types that implement `Copy` (integers, booleans, char, and tuples of Copy types) are bit-copied instead of moved, so the original stays valid. Understanding this split is the first hurdle: trying to use a moved value produces the classic 'value borrowed here after move' error.

Borrowing lets you access a value without taking ownership by passing a reference. There are two kinds. A shared reference `&T` grants read-only access and you may have many of them at once. A mutable reference `&mut T` grants read-write access but is exclusive: while it exists, no other reference of any kind to that value may exist. This is the borrow rule - any number of readers, or exactly one writer, never both. It is enforced at compile time and eliminates data races by construction.

The borrow checker also ensures no reference outlives the data it points to, which connects ownership to lifetimes. A reference is always tied to the scope of its owner, so you cannot return a reference to a local that is about to be dropped.

Newcomers fight the borrow checker by cloning everything to dodge errors, which works but discards Rust's performance advantage and signals a model not yet internalized. The idiomatic path is to borrow where you only need to read, take `&mut` where you need to mutate in place, and move ownership only when the callee genuinely needs to keep the value. Once the rules click, ownership stops being a fight and becomes a precise way to express who is responsible for each resource.

Common Misconception

Assigning one variable to another copies the value like in most languages. In reality non-Copy types are moved, invalidating the original binding so you cannot use it afterward.

Why It Matters

Misunderstanding moves and borrows leads to either compile errors that block all progress or reflexive cloning that throws away Rust's zero-cost, race-free memory model.

Common Mistakes

  • Using a value after it was moved into another variable or passed by value to a function.
  • Cloning everywhere to silence borrow errors instead of passing a reference where only read access is needed.
  • Holding a shared reference while trying to take a mutable reference to the same value, violating the exclusivity rule.
  • Returning a reference to a local variable that is dropped at the end of the function.
  • Forgetting that &mut is exclusive, so iterating over a collection while mutating it fails to compile.

Avoid When

  • A function genuinely needs to take ownership and store the value, where moving by value is correct rather than borrowing.
  • The value is a small Copy type like an integer, where copying is cheaper and clearer than threading a reference.
  • Reaching for shared mutability across threads, where Arc and Mutex or interior mutability are the right tools instead of plain references.

When To Use

  • Passing a value to a function that only reads it, where a shared reference avoids an unnecessary move or clone.
  • Mutating a collection or struct in place via an exclusive mutable reference instead of returning a new copy.
  • Designing APIs that make ownership transfer explicit so callers know who is responsible for a resource.
  • Avoiding clones in hot paths by borrowing data that lives long enough to satisfy the borrow checker.

Code Examples

✗ Vulnerable
fn print_len(s: String) {
    println!("length is {}", s.len());
}

fn main() {
    let name = String::from("alice");

    // Moves name into print_len; name is invalid afterward.
    print_len(name);

    // Error: value borrowed here after move.
    println!("name was {}", name);

    let mut total = vec![1, 2, 3];
    let first = &total[0]; // shared borrow
    // Error: cannot borrow total as mutable while a shared borrow is live.
    total.push(4);
    println!("{}", first);
}
✓ Fixed
// Borrow instead of taking ownership so the caller keeps the value.
fn print_len(s: &String) {
    println!("length is {}", s.len());
}

fn main() {
    let name = String::from("alice");

    print_len(&name);
    // name is still valid because we only borrowed it.
    println!("name was {}", name);

    let mut total = vec![1, 2, 3];
    // Finish reading before mutating: copy the small value out.
    let first = total[0];
    total.push(4);
    println!("first was {}, vec is now {:?}", first, total);
}

Added 8 Jun 2026
Views 3
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 1 ping M 2 pings T 0 pings W
No pings yet today
Google 1 Scrapy 1
Google 2 Scrapy 1
crawler 3
DEV INTEL Tools & Severity
🟡 Medium ⚙ Fix effort: Medium
⚡ Quick Fix
Pass &value to borrow for read access or &mut value to mutate in place, and only move by value when the callee must own the data.
📦 Applies To
web cli queue-worker library
🔍 Detection Hints
borrow of moved value|value borrowed here after move|cannot borrow .* as mutable
Auto-detectable: ✓ Yes rustc
🤖 AI Agent
Confidence: High False Positives: Medium ✗ Manual fix Fix: Medium Context: Function

✓ schema.org compliant