Skip to content

The Rust Mindset

Before Project 1, spend ten minutes on the one idea the whole language hangs from. If ownership clicks, the borrow checker stops feeling like a bouncer and starts feeling like a pair programmer who has already spotted the bug you were about to write. This page is that click.

Every value in Rust has exactly one owner — the variable responsible for cleaning it up. When the owner goes out of scope, the value is freed. That’s it. Everything else — moves, borrows, lifetimes — is machinery to keep that one rule true while still letting you write useful programs.

{
let s = String::from("hi"); // s owns the heap buffer
...
} // s goes out of scope → buffer freed, automatically

No garbage collector, no manual free(). The compiler inserts the cleanup because it knows who the owner is. The cost it charges for that: you have to make ownership unambiguous, and that’s what the borrow checker is checking.

Assigning or passing a heap value moves ownership; the old binding is invalidated:

let a = String::from("hi");
let b = a; // ownership MOVES from a to b
println!("{a}"); // ❌ compile error: a no longer owns anything

This looks hostile until you see what it prevents: if both a and b “owned” the buffer, both would try to free it at scope end — a double-free, a classic C bug. Rust makes that un-writable. If you genuinely want two independent copies, you ask: let b = a.clone(); — explicit, visible, and you can see the cost in the code.

You rarely want to give away ownership just to read something. So you borrow a reference instead:

fn length(s: &String) -> usize { s.len() } // borrows, doesn't take ownership
let s = String::from("hello");
let n = length(&s); // lend s
println!("{s}: {n}"); // ✓ s is still ours

The borrow checker enforces two rules on references, and they are the entire game:

Either one mutable borrow (&mut), or any number of shared borrows (&) — never both at once.

let mut v = vec![1, 2, 3];
let r = &v; // shared borrow
v.push(4); // ❌ can't mutate while r is borrowing it
println!("{r:?}");

Try it: dial the number of shared borrows up and down and flip the mutable borrow on and off. The only combination the compiler rejects is “a &mut and one-or-more & at the same time” — that’s “shared XOR mutable” made tangible.

Borrow-checker rule visualizer
Shared borrows &x
let mut x = 5;
let r1 = &x;        // shared borrow #1
let m = &mut x;     // mutable borrow
This is the shared XOR mutable rule: you may have either any number of shared borrows & or exactly one mutable borrow &mut — never both at once. Mixing them is the only invalid combination, and it's exactly what prevents data races and dangling references.

Under the hood — what that rule actually buys

Section titled “Under the hood — what that rule actually buys”

That single “shared XOR mutable” rule is not bureaucracy; it’s the thing that makes Rust’s promises true:

  • No dangling pointers. A reference can’t outlive the value it points to (the compiler tracks this with lifetimes), so you can never read freed memory.
  • No data races, at compile time. A data race needs two threads, shared data, and at least one writer, with no synchronization. “Shared XOR mutable” makes that combination impossible to express — which is why Rust calls it fearless concurrency. You’ll feel this directly in kvlite.

Here’s the mental shift that makes Rust learnable: the compiler is not grading you, it’s teaching you. rustc’s errors are unusually specific — they name the value, the borrow, and often the fix. The fastest way to learn is to write code you think is right, let the compiler object, and read the objection. That loop only works if you’re building, which is the whole premise of this playbook.

What does building this force you to understand — and what is Rust’s compiler protecting you from? From page one, the answer is the same shape: the compiler is keeping the “one owner” rule true so memory is freed exactly once and never used after, and keeping “shared XOR mutable” true so concurrency can’t race. You satisfy it by making ownership explicit. That’s the entire tax — and in return you get C-level speed with none of C’s memory bugs. Now go build something: Project 1 · logwise →

  1. State Rust’s single ownership rule in one sentence. What does the compiler do when an owner goes out of scope?
  2. Why does let b = a; (where a is a String) make a unusable afterward — and what real bug is that preventing?
  3. Why does an i32 copy on assignment while a String moves? What trait marks the difference?
  4. State the borrow checker’s “shared XOR mutable” rule. Name the two guarantees it buys you.
  5. The page claims the compiler is “teaching, not grading.” What concrete working habit does that imply for learning Rust fast?
Show answers
  1. Every value has exactly one owner, the variable responsible for cleaning it up; when the owner goes out of scope, Rust automatically frees the value (no GC, no manual free).
  2. Assigning a non-Copy heap value moves ownership to b, so a no longer owns anything. It prevents a double-free: if both a and b owned the buffer, both would try to free it at scope end.
  3. i32 is small, stack-only, and implements the Copy trait, so duplicating its bytes is trivially safe; String owns a heap buffer and is not Copy, because a byte-copy would create two owners of one buffer (so it moves instead).
  4. Either one mutable borrow (&mut) or any number of shared borrows (&), never both at once. It buys no dangling pointers (a reference can’t outlive its value) and no data races at compile time (fearless concurrency).
  5. Write the code you think is right and read the compiler’s error instead of trying to satisfy the checker up front — learn in the fast feedback loop with rustc, which only runs if you’re actually building and typing.