Reading Rust

Part 1 · The runtime machine

The four verbs: move, Copy, Clone, Drop

Everything that can happen to a value when it changes hands. Three of these are free; the one people fear is the one they reach for most.

40 minproductive failurecontrasting casesPOE
What this lesson is trying to break (4)
  • `Copy` means "this type gets copied instead of moved".
  • `.clone()` is always expensive.
  • You can call `.drop()` on a value to destroy it early.
  • Cloning to satisfy the borrow checker is always a hack.

Before any explanation, try this.

Attempt this first — it is meant to be hardwrite it — thinking it does not count

Here is a function that does not compile:

fn total_length(items: Vec<String>) -> usize {
    let mut n = 0;
    for s in items {
        n += s.len();
    }
    println!("{} items measured", items.len());   // error here
    n
}

Without looking anything up: why does the last line fail, and write down two different fixes. Then say which fix you would choose and what it costs.

Do not skip this. Getting it wrong now is worth more than reading the answer cleanly — attempting a problem before instruction reliably beats being taught first, as long as you actually attempt it.

The for s in items loop consumes items. for calls IntoIterator::into_iter, and for Vec<String> that takes the vector by value — so the loop owns it, and after the loop the binding is dead. Two obvious fixes are for s in &items (borrow instead of consume) and calling items.len() before the loop. A third is items.clone(), which works and costs a full deep copy of every string.

Which one is right depends on the four verbs.

The four verbs

Every time a value changes hands in Rust, exactly one of these happens.

Varies: what happens to the original — the destination always ends up with a usable value

VerbWritten asSource afterwardsCostChosen by
Movelet b = a;dead — compiler refuses useshallow byte copy, often optimised awaythe compiler, by default
Copylet b = a;still aliveidentical to a movethe type, via impl Copy
Clonelet b = a.clone();still alivewhatever clone does — free to arbitraryyou, explicitly
Dropend of scopedestroyedwhatever drop doesthe compiler, at scope end

Notice that Move and Copy are the same operation. They differ only in whether the compiler kills the source afterwards.

That last line is the insight, and it is the opposite of how Copy is usually described.

Copy does not mean “copied instead of moved”

Unlearn thiscarried over from C++, or from tutorials that say 'Copy types are copied'
You think

Rust has two kinds of assignment. Big types like String get moved, and small types like i32 get copied — a different, cheaper operation chosen because they are small.

Wrong here

There is only one operation. let b = a; always does the same thing: copy the value’s bytes into the new place. Size has nothing to do with it — [u8; 4096] is Copy and Box<u8> is not.

Instead

Copy is a claim about correctness, not about cost. It says: duplicating these bytes yields a second, equally valid value. When that claim holds, the compiler has no reason to invalidate the source, so it does not. Move is what happens when the claim does not hold.

Ask why String cannot be Copy and the answer falls straight out of 1.1. Duplicating a String’s three words gives you two headers pointing at one heap buffer. Both would free it. The bytes duplicated fine; the meaning did not.

So the rule is:

A type can be Copy only if duplicating its bytes is semantically harmless — which means it owns no resource that needs releasing.

And therefore a type can never be both Copy and Drop. The compiler enforces this directly:

#[derive(Clone, Copy)]
struct Guard;

impl Drop for Guard {          // error[E0184]: the trait `Copy`
    fn drop(&mut self) {}      // cannot be implemented for this type;
}                              // it has a destructor
Predict firstcommit in writing, then look
#[derive(Clone, Copy)]
struct Point { x: f64, y: f64 }

struct Label { text: String }

fn main() {
    let p = Point { x: 1.0, y: 2.0 };
    let q = p;
    println!("{} {}", p.x, q.x);        // line A

    let l = Label { text: "hi".into() };
    let m = l;
    println!("{} {}", l.text, m.text);  // line B
}

Clone is not “expensive copy”

The second bad heuristic. Clone means duplicate this correctly, and how expensive that is depends entirely on the type.

Varies: only the type being cloned — the call is `.clone()` in every row

TypeWhat .clone() doesCost
i32copies 4 bytesfree (and redundant — it is Copy)
Rc<T> / Arc<T>copies a pointer, increments a countervery cheap — no T is duplicated
Stringallocates, copies the bytesproportional to length
Vec<String>allocates, then clones every elementproportional to total size
&Tcopies the referencefree — references are Copy

`Arc::clone` and `Vec::clone` are the same method name and differ by orders of magnitude. This is why reviewers ask what is being cloned rather than whether cloning is happening.

Because of this, Arc::clone(&x) is often written in preference to x.clone() even though they are identical. The explicit form tells the reader this is a refcount bump, not a deep copy. That is the social machine at work — a convention carrying information the type system already had.

And a practical note for someone coming from garbage-collected languages, since this comes up constantly in code review:

Cloning to get past the borrow checker is not automatically wrong. A .clone() of a short string at a boundary, once per request, is invisible. The same call inside a hot loop is not. “Don’t clone” is not a rule — “know what you cloned and how often” is.

Being able to say that calmly is a tech-lead skill. Rust newcomers tend to oscillate between cloning everything and refusing to clone anything, and both are expensive in different ways.

Drop, and the empty function that destroys things

Drop is Rust’s destructor. It runs automatically when a place goes out of scope, and you cannot call it yourself:

let s = String::from("hi");
s.drop();     // error[E0040]: explicit use of destructor method

You call drop(s) instead — and this is where the mechanism becomes clear, because here is the entire implementation from the standard library:

pub fn drop<T>(_x: T) {}

An empty body. It works because the parameter is taken by value: calling drop(s) moves s into _x, and _x’s place ends immediately at the closing brace. The destruction comes from the move plus the scope end — nothing in the function does anything at all.

If that made sense on first reading, Part 1 has landed. If it did not, go back to 1.1 rather than pressing on; everything in Part 2 assumes it.

For Go programmers there is a clean comparison: Drop is defer, except it is attached to the type instead of written at the call site, so it cannot be forgotten. That is the RAII pattern, and it is what makes MutexGuard, File and Vec all release their resources without any explicit close.

Back to the opening problemwrite it — thinking it does not count

Return to total_length at the top of this lesson. You now have four verbs.

Rewrite the signature and the loop so nothing is cloned and nothing is consumed, then explain in one sentence what changed about who owns the vector.

What you should now be able to say

  • Move and Copy are the same machine operation; they differ in whether the source stays usable.
  • Copy is a claim that a bitwise duplicate is correct, not that it is cheap. Hence: no type is both Copy and Drop.
  • Clone is explicit and its cost is the type’s business — Arc::clone is a counter increment, Vec<String>::clone is a deep copy.
  • drop(x) destroys x by moving it into a function whose body is empty.
  • Drop is defer bound to the type instead of the call site.

Next: 1.4 — Where values die, which is about the exact moment drop happens, and the deadlock that catches everyone once.

In the wildgo read the real thing now, while it is fresh
  • std::mem::drop — the whole functionSearch for `pub fn drop`. The body is empty. Understanding why an empty function destroys its argument is a genuinely good test of whether Part 1 has landed.
  • Rc::clone — what a "clone" can actually beFind the `impl Clone for Rc`. It increments a counter and copies a pointer. This is why "clone is expensive" is a bad heuristic — Clone means "duplicate correctly", not "duplicate deeply".
Scheduled for recall4 items added to your review queue

These are not shown to you now on purpose. They will surface in Review tomorrow, mixed in among items from other parts of the course — not grouped by topic. Sorting out which idea applies is the part that transfers.