Chapter 20: Move Semantics, Copy, Clone, and Drop
Prerequisites
You will understand
- Move vs Copy vs Clone — three distinct events
- Why
CopyandDropcannot coexist - When
.clone()is deliberate vs a code smell
Reading time
Box, Rc, RefCell) change the ownership shape — they are the engineered alternatives to default move/drop semantics.
Ch 30: Smart Pointers →
Move, Copy, Clone, and Drop Are Different Events
Why Copy and Drop Cannot Coexist
#![allow(unused)]
fn main() {
let a: i32 = 42;
let b = a; // Copy — a is still valid
println!("{a}");
let s1 = String::from("hello");
let s2 = s1; // Move — s1 invalidated
// println!("{s1}"); // E0382
let s3 = s2.clone(); // Clone — explicit deep copy
println!("{s2} {s3}");
}
i32 implements Copy. Stack-only, bitwise copy. Both valid.
String is not Copy. Assignment transfers ownership. s1 dead.
.clone() duplicates heap data. Now two independent owners with separate allocations.
In Your Language: Move vs Copy
#![allow(unused)]
fn main() {
let a = 42; // Copy (i32 is Copy)
let b = a; // both valid
let s = String::from("x");
let t = s; // Move — s is dead
let u = t.clone(); // Clone — explicit copy
}
a := 42
b := a // value copy (always)
s := "hello"
t := s // both valid (GC manages)
// No move concept — everything is copied or ref-counted
Three-Level Explanation
Beginner: When you assign a String to another variable, the original becomes invalid. Think of it like passing a physical key — only one person can hold it. For simple numbers (i32, bool), Rust copies them automatically because they’re cheap.
Engineer: Types that are Copy (all scalar types, &T) are bitwise-copied on assignment. Non-Copy types (anything owning heap data: String, Vec, Box) are moved — the source is invalidated. .clone() performs a deep copy by calling the Clone trait implementation, allocating new heap memory.
Deep Dive: At the MIR level, a move is a memcpy of the stack representation followed by marking the source as uninitialized. The compiler inserts drop flags (bool) to track whether a binding is live. Copy is a marker trait with no methods — it simply tells the compiler “bitwise copy is semantically correct for this type.” Clone is a regular trait with fn clone(&self) -> Self. Types can be Clone without being Copy when they need custom duplication logic (e.g., allocating new heap memory).
Readiness Check - Transfer Semantics Mastery
| Skill | Level 0 | Level 1 | Level 2 | Level 3 |
|---|---|---|---|---|
| Distinguish move/copy/clone | I mix them up | I can name each one | I can predict which event happens at assignment/call sites | I can design APIs that express transfer intent clearly |
| Use clone intentionally | I add clone to silence errors | I know clone creates a duplicate | I can justify each clone by ownership need or boundary crossing | I can remove unnecessary clones in hot paths |
| Reason about drop safety | I treat cleanup as hidden behavior | I know drop runs at scope end | I can explain why Copy and Drop conflict | I can model teardown order in composed types |
Target Level 2+ before moving to borrow-checker internals.
Compiler Error Decoder - Move and Drop Semantics
| Error code | What it usually means | Typical fix direction |
|---|---|---|
| E0382 | Used value after it moved | Borrow instead, reorder usage before move, or clone intentionally |
| E0505 | Tried to move a value while references to it are still live | End borrows first, then move; or clone for independent ownership |
| E0509 | Tried to move out of a type that implements Drop | Borrow fields, use explicit extraction patterns, or redesign data ownership |
First diagnose the transfer event, then decide whether ownership should move, be borrowed, or be duplicated.
Step 1 - The Problem
Once you know that ownership can move, the next questions are:
- when is assignment a move?
- when is assignment a copy?
- when should duplication be explicit?
- how does cleanup interact with all of this?
Many languages blur these differences. Rust does not, because resource safety depends on them.
Step 2 - Rust’s Design Decision
Rust split value transfer into distinct semantic categories:
- move for ownership transfer
Copyfor cheap implicit duplicationClonefor explicit duplicationDropfor cleanup logic
Rust accepted:
- more traits and explicitness
- friction when trying to “just duplicate” resource-owning values
Rust refused:
- implicit deep copies
- ambiguous destructor behavior
- accidental duplication of resource owners
Step 3 - The Mental Model
Plain English rule:
- move transfers ownership
Copyduplicates implicitly because duplication is cheap and safeCloneduplicates explicitly because the cost or semantics matterDropruns when ownership finally ends
Step 4 - Minimal Code Example
#![allow(unused)]
fn main() {
let x = 5i32;
let y = x;
println!("{x} {y}");
}
Step 5 - Line-by-Line Compiler Walkthrough
i32isCopy.let y = x;duplicates the bits.- Both bindings remain valid because duplicating an
i32does not create resource-ownership ambiguity.
Now compare:
#![allow(unused)]
fn main() {
let s1 = String::from("hi");
let s2 = s1;
}
Stringis notCopy.s2 = s1is a move.s1is invalidated because two liveStringowners would double-drop the same heap buffer.
Step 6 - Three-Level Explanation
Level 1 - Beginner
Small simple values like integers get copied automatically. Resource-owning values like String get moved by default.
Level 2 - Engineer
Choose Copy only for types where implicit duplication is cheap and semantically unsurprising. Use Clone when duplication is meaningful enough that callers should opt in visibly. That is why String is Clone but not Copy.
Level 3 - Systems
Copy is a semantic promise:
- bitwise duplication preserves correctness
- implicit duplication is acceptable
- there is no destructor logic requiring unique ownership
This is why Copy and Drop are incompatible. A type with destructor semantics cannot be safely or meaningfully duplicated implicitly without changing cleanup behavior.
What Can Be Copy?
Usually:
- integers
- floats
- bool
- char
- plain references
- small structs/enums whose fields are all
Copy
Usually not:
StringVec<T>Box<T>- file handles
- mutex guards
- anything implementing
Drop
Clone
Clone is explicit:
#![allow(unused)]
fn main() {
let a = String::from("hello");
let b = a.clone();
}
That explicitness matters because:
- duplication may allocate
- duplication may be expensive
- duplication may reflect a design choice the reader should notice
In good Rust code, clone() is not shameful. It is shameful only when used to avoid understanding ownership or when sprayed blindly through hot paths.
Drop
Drop ties cleanup to ownership end. It completes the model:
- move changes who will be dropped
Copymakes extra identical values that each need no special cleanupClonecreates a fresh owned value with its own later dropDropcloses the lifecycle
ManuallyDrop<T> exists for advanced low-level code that must suppress automatic destruction temporarily, but that belongs mostly in systems internals rather than ordinary application logic.
Step 7 - Common Misconceptions
Wrong model 1: “Copy is just a performance optimization.”
Correction: it is also a semantic choice about ownership behavior.
Wrong model 2: “If cloning makes the code compile, it must be the right fix.”
Correction: it may be the right ownership model, or it may be papering over poor structure.
Wrong model 3: “Moves are expensive because data is moved.”
Correction: moves often transfer small owner metadata, not the whole heap allocation.
Wrong model 4: “Copy and Drop could work together if the compiler were smarter.”
Correction: they encode conflicting semantics around implicit duplication and destruction.
Step 8 - Real-World Pattern
Reading strong Rust code means noticing:
- when an API returns owned versus borrowed data
- whether a type derives
Copyfor ergonomic value semantics - whether
clone()is deliberate or suspicious - where destructor-bearing values are scoped tightly
These choices signal both performance intent and API taste.
Step 9 - Practice Block
Code Exercise
Define one small plain-data struct that can derive Copy and one heap-owning struct that should only derive Clone. Explain why.
Code Reading Drill
What ownership events happen here?
#![allow(unused)]
fn main() {
let a = String::from("x");
let b = a.clone();
let c = b;
}
Spot the Bug
Why can this type not be Copy?
#![allow(unused)]
fn main() {
struct Temp {
path: String,
}
impl Drop for Temp {
fn drop(&mut self) {}
}
}
Refactoring Drill
Take code with repeated .clone() calls and redesign one layer so a borrow or ownership transfer expresses the same intent more clearly.
Compiler Error Interpretation
If the compiler says use of moved value, translate that as: “this value was non-Copy, so assignment or passing consumed the old owner.”
Step 10 - Contribution Connection
After this chapter, you can read:
- ownership-sensitive APIs
- derive choices around
CopyandClone - code review comments about accidental cloning
- destructor-bearing helper types
Good first PRs include:
- removing unjustified
Copyderives - replacing clone-heavy code with borrowing or moves
- documenting why a type is
Clonebut intentionally notCopy
In Plain English
Rust separates moving, implicit copying, explicit copying, and cleanup because those actions mean different things for real resources. That matters because software breaks when ownership changes are invisible or when expensive duplication happens casually.
What Invariant Is Rust Protecting Here?
Implicit duplication must only exist when it cannot create resource-ownership ambiguity, and cleanup must still happen exactly once for each distinct owned resource.
If You Remember Only 3 Things
- Move is ownership transfer.
Copyis implicit duplication for cheap, destructor-free value semantics.Cloneis explicit duplication because the cost or meaning matters.
Memory Hook
Move is handing over the only house key. Copy is photocopying a public handout. Clone is ordering a second handcrafted item from the workshop.
Flashcard Deck
| Question | Answer |
|---|---|
| What does move mean? | Ownership transfers to a new binding or callee. |
What does Copy mean? | The value may be duplicated implicitly because that is cheap and semantically safe. |
Why is String not Copy? | It owns heap data and implicit duplication would create double-drop ambiguity. |
Why is Clone explicit? | Duplication may allocate or carry semantic cost. |
Can a Drop type also be Copy? | No. Destructor semantics conflict with implicit duplication. |
Are references Copy? | Yes, shared references are cheap, non-owning values. |
What is a common smell involving clone()? | Using it as a reflex to silence ownership confusion. |
What does ManuallyDrop do conceptually? | It suppresses automatic destruction until low-level code chooses otherwise. |
Chapter Cheat Sheet
| Operation | Meaning | Typical cost story |
|---|---|---|
assignment of Copy type | implicit duplicate | cheap value copy |
assignment of non-Copy type | move | ownership transfer |
.clone() | explicit duplicate | may allocate or do real work |
| scope exit | Drop runs | cleanup of owned resources |
Drop + implicit copy | forbidden | would break destructor semantics |