Last lesson left a question: why do some types move and others copy? The answer is two traits — Copy and Clone — and the line between them is one of Rust's cleanest design decisions.
Copy — implicit, free, stack-only
Types that live entirely on the stack and are trivially duplicable implement the Copy trait: all the integer and float types, bool, char, and tuples or arrays made only of Copy types. For these, let y = x; makes a bitwise copy and leaves x perfectly valid. No move, no fuss — duplicating eight bytes is cheaper than tracking ownership.
Clone — explicit, possibly expensive
Types that own heap data (String, vectors, hash maps) do not implement Copy, because duplicating them means a heap allocation and a byte-for-byte copy. That cost should never be invisible, so Rust makes you ask for it by name: let s2 = s1.clone();. Now both own independent buffers — and you chose that.
.clone(). This is why Rust code's performance is so readable — the costly operations are spelled out, not buried in an assignment operator.Deriving them
For your own structs, you opt in with #[derive(Clone)] — and #[derive(Copy)] if every field is itself Copy. That one line tells the compiler to generate the duplication logic for you. You'll see #[derive(Clone, Debug)] on top of struct definitions constantly.
The habit to build
When you hit a move error, the lazy fix is to sprinkle .clone() until it compiles. Resist that. Each clone is a real allocation. Often the right answer is a reference — the next track — which borrows the data instead of duplicating it.