Skip to content

SOP: Ownership & Borrowing ​

Updated Mar 2026

Overview ​

Rust's ownership system is the foundation of memory safety without garbage collection. This SOP covers the rules, patterns, and how to satisfy the borrow checker.

The Three Rules ​

Move vs Copy vs Clone ​

Move Semantics ​

rust
fn main() {
    let s1 = String::from("hello");
    let s2 = s1;            // s1 is MOVED to s2
    // println!("{s1}");     // ERROR: s1 no longer valid

    let x: i32 = 5;
    let y = x;              // x is COPIED (i32 implements Copy)
    println!("{x} {y}");    // Both valid
}

Clone for Explicit Deep Copy ​

rust
fn main() {
    let s1 = String::from("hello");
    let s2 = s1.clone();    // Explicit deep copy
    println!("{s1} {s2}");  // Both valid
}

Borrowing Rules ​

Shared References ​

rust
fn calculate_length(s: &String) -> usize {
    s.len()  // Can read but not modify
}

fn main() {
    let s = String::from("hello");
    let len = calculate_length(&s);  // Borrow s
    println!("{s} has length {len}"); // s still valid
}

Mutable References ​

rust
fn append_world(s: &mut String) {
    s.push_str(", world");
}

fn main() {
    let mut s = String::from("hello");
    append_world(&mut s);
    println!("{s}");  // "hello, world"
}

The Exclusivity Rule ​

rust
fn main() {
    let mut s = String::from("hello");

    let r1 = &s;      // OK — first shared borrow
    let r2 = &s;      // OK — second shared borrow
    println!("{r1} {r2}");
    // r1 and r2 no longer used after this point (NLL)

    let r3 = &mut s;  // OK — no shared borrows alive
    println!("{r3}");
}

Common Patterns ​

Pattern: Returning References with Lifetimes ​

rust
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

fn main() {
    let s1 = String::from("long string");
    let result;
    {
        let s2 = String::from("xyz");
        result = longest(s1.as_str(), s2.as_str());
        println!("{result}");  // Must use while s2 is alive
    }
    // result can't be used here — s2 is dropped
}

Pattern: Struct Borrowing ​

rust
struct Excerpt<'a> {
    text: &'a str,  // Struct holds a reference — needs lifetime
}

impl<'a> Excerpt<'a> {
    fn level(&self) -> i32 {
        3  // No reference in return — no lifetime needed
    }

    fn announce(&self, announcement: &str) -> &str {
        // Lifetime elision: output gets &self's lifetime
        self.text
    }
}

Pattern: Borrowing in Loops ​

rust
fn main() {
    let mut data = vec![1, 2, 3, 4, 5];

    // Immutable iteration — borrows data
    for item in &data {
        println!("{item}");
    }

    // Mutable iteration — mutably borrows data
    for item in &mut data {
        *item *= 2;
    }

    // Consuming iteration — moves data
    for item in data {
        println!("{item}");
    }
    // data is no longer valid here
}

Troubleshooting the Borrow Checker ​

ErrorCauseFix
"cannot move out of borrowed content"Trying to take ownership from a referenceClone, or restructure to use references
"cannot borrow as mutable more than once"Two &mut to same dataScope the borrows so they don't overlap
"cannot borrow as mutable because also borrowed as immutable"& and &mut coexistFinish using & before taking &mut
"does not live long enough"Reference outlives dataExtend data's lifetime or clone
"missing lifetime specifier"Compiler can't infer lifetimesAdd explicit lifetime annotations

Decision Checklist ​

  • [ ] Determine if you need to transfer ownership or borrow
  • [ ] If borrowing, determine if you need shared (&T) or exclusive (&mut T) access
  • [ ] Check that no &mut overlaps with any other borrow of the same data
  • [ ] For returned references, verify lifetime annotations are correct
  • [ ] For struct fields that are references, add lifetime parameters
  • [ ] Prefer owned types (String, Vec) over references when lifetime complexity is high

Built with VitePress | Rust SOP Documentation