Ownership

This is the thing Rust is famous for. The borrow checker is also the thing agents stumble over most. The mental model is small. Once you have it, the compiler errors stop being mysterious.

Three rules

  1. Every value has exactly one owner. When the owner goes out of scope, the value is dropped (freed, closed, released).
  2. You can have many shared (read-only) references at once. &T everywhere.
  3. You can have one exclusive (read-write) reference at a time, and only when no shared references exist. &mut T is alone.

Rules 2 and 3 are mutually exclusive: shared OR exclusive, never both.

Stack vs heap, briefly

StackHeap
WhereFunction-local memoryLong-lived allocations
CostFree (just a pointer)Allocation + bookkeeping
Examplesi32, bool, f64, fixed-size arraysString, Vec<T>, Box<T>, HashMap<K, V>

String is heap-allocated. &str is a pointer + length on the stack pointing into something else's heap. That distinction matters.

Move: ownership changes hands

let s = String::from("hello");
let t = s;            // ownership moves from s to t
println!("{}", t);    // ok
println!("{}", s);    // error: value used after move

After the assignment, s no longer owns anything. Trying to use it is a compile error. This is the borrow checker doing its job.

Copy: stack values pretend nothing happened

Types implementing the Copy trait don't move; they copy. Integers, booleans, characters, fixed arrays of Copy types.

let n = 5;
let m = n;            // copy, not move
println!("{} {}", n, m);  // both fine

Clone: explicit deep copy

let s = String::from("hello");
let t = s.clone();    // both now own independent copies
println!("{} {}", s, t);

.clone() is explicit. Rust never clones for you. That is a feature: you see the cost where it happens.

Borrow: read access without taking ownership

fn print(s: &str) {
    println!("{}", s);
}
 
let s = String::from("hello");
print(&s);            // borrow
println!("{}", s);    // still ok, never moved

&str is a borrowed view into a string. print reads it without taking ownership, and s remains usable.

This is the default. Most function arguments should be borrows (&T) unless the function genuinely needs to consume or modify the value.

Mutable borrow: temporary write access

fn push(v: &mut Vec<i32>, n: i32) {
    v.push(n);
}
 
let mut v = vec![1, 2, 3];
push(&mut v, 4);
println!("{:?}", v);  // [1, 2, 3, 4]

The &mut borrow is exclusive: while push runs, no other reference to v exists. When push returns, the borrow ends.

Slices: borrowed views into collections

let v = vec![1, 2, 3, 4, 5];
let slice: &[i32] = &v[1..4];
println!("{:?}", slice);  // [2, 3, 4]

&[T] and &str are slices: pointer + length into something else's memory. They are how you pass a "view" of a collection without copying it.

Most function arguments that accept a sequence should take &[T] or &str, not Vec<T> or String. Owners can be borrowed; borrows cannot be promoted.

Lifetimes: how long a reference is valid

fn longest<'a>(s1: &'a str, s2: &'a str) -> &'a str {
    if s1.len() >= s2.len() { s1 } else { s2 }
}

'a says: "the returned reference is valid as long as both s1 and s2 are valid." Without it, the compiler couldn't be sure which input the output borrowed from.

In practice, lifetimes are almost always elided: the compiler infers them. You only write them when the elision rules don't cover your case. For most code, you do not type 'a.

When you see 'static: that means "for the entire program." Be skeptical when it shows up. Box::leak and &'static str literals are the legitimate cases. Random 'statics creeping in late are usually papering over a real lifetime problem.

What this means for agent-written code

Most borrow checker errors come from one of these mistakes:

SymptomThe real issue
"value used after move"The function took ownership when a borrow would have worked.
"cannot borrow as mutable because it is also borrowed as immutable"Two references exist when the rules require only one.
"borrowed value does not live long enough"A reference outlives the data it points to.
"cannot move out of borrowed content"You tried to take ownership through a &self.

The lazy fix is .clone(). The right fix is to think about who needs ownership and who needs a borrow, and rewrite the signatures.