The assignment that breaks
You write a function that takes a String, passes it to another function, and then tries to print it again. The compiler immediately rejects you with a use of moved value error. You stare at the screen. In Python or JavaScript, assigning a variable to another variable just creates a new reference or copies the value. The original stays perfectly usable. Rust refuses to play along.
The language forces you to make an explicit choice about duplication. You either accept that ownership transferred, or you pay for a copy. That choice splits into two distinct paths: Copy and Clone. They sound similar. They behave completely differently. Understanding the boundary between them removes one of the most common friction points when learning Rust.
Stop fighting the implicit behavior. Learn when the compiler allows free duplication and when it demands you pay for it.
How Rust decides what to do
Rust tracks ownership at compile time. Every value has exactly one owner. When you write let b = a;, the compiler looks at the type of a and asks a single question: does this type implement the Copy trait?
If the answer is yes, the compiler performs a bitwise copy. It reads the bytes from a and writes them to b. Both variables remain valid. The original stays alive. This is what happens with integers, booleans, characters, and floating point numbers.
If the answer is no, the compiler moves the value. It transfers ownership from a to b. The original variable becomes invalid. The compiler will reject any subsequent use of a with E0382 (use of moved value). This is what happens with String, Vec, HashMap, and most custom structs.
To duplicate a non-copy type, you must call .clone(). The Clone trait requires an explicit method call. The compiler will not do it for you. This design prevents accidental performance penalties. Deep copying heap data is expensive. Rust forces you to acknowledge the cost.
Treat .clone() as a deliberate performance decision. The compiler will not hide it from you.
The minimal example
Here is the exact pattern that trips up beginners. Watch how the compiler treats each assignment.
fn main() {
// String allocates heap memory. It does not implement Copy.
let s1 = String::from("hello");
// This moves ownership. s1 is now invalid.
let s2 = s1;
// Uncommenting this line triggers E0382.
// println!("s1 = {s1}");
// Integers live entirely on the stack. They implement Copy.
let x = 42;
// This performs a bitwise copy. x remains valid.
let y = x;
// Both variables are usable. No ownership transfer occurred.
println!("x = {x}, y = {y}");
}
The code compiles because we never use s1 after the move. If we remove the comment on the println!, the compiler stops you. It refuses to guess whether you intended to move or copy. You must be explicit.
Trust the borrow checker here. It is protecting you from silent performance traps and dangling references.
What the compiler actually checks
The Copy trait is a marker trait. It contains no methods. Its only job is to tell the compiler that bitwise duplication is safe for this type. The Clone trait contains a single method: fn clone(&self) -> Self. Types implement Clone by defining how to duplicate their internal state.
There is a strict hierarchy between them. Every Copy type must also implement Clone. The compiler enforces this automatically. If you derive Copy, you get Clone for free. The reverse is not true. A type can implement Clone without implementing Copy.
The compiler checks three conditions before allowing Copy:
- The type must not implement the
Droptrait. If a type cleans up resources on destruction, bitwise copying would cause double-free bugs. - Every field inside the type must itself be
Copy. - The type must not contain references or smart pointers that manage shared ownership.
These rules guarantee that Copy is always a zero-cost operation. The compiler can emit a simple memcpy instruction. No heap allocation. No reference counting. No method dispatch.
When you see Copy, think stack-only and trivially duplicable. When you see Clone, think explicit duplication with potential heap cost.
Real-world structs and the derive macro
You will rarely write manual Clone or Copy implementations for simple data structures. The #[derive] macro handles it. The syntax looks identical, but the compiler applies different rules.
// This struct contains only Copy types.
// The compiler allows bitwise duplication.
#[derive(Debug, Clone, Copy)]
struct Point {
x: f64,
y: f64,
}
// This struct owns heap memory.
// The compiler requires explicit cloning.
#[derive(Debug, Clone)]
struct User {
name: String,
tags: Vec<String>,
}
fn main() {
let p1 = Point { x: 1.0, y: 2.0 };
let p2 = p1; // Bitwise copy. p1 stays valid.
let u1 = User {
name: String::from("Alice"),
tags: vec!["admin"],
};
let u2 = u1.clone(); // Deep copy. u1 stays valid.
// let u3 = u1; // This would move u1 and invalidate it.
println!("{p1:?}, {p2:?}");
println!("{u1:?}, {u2:?}");
}
Notice the convention in the first derive macro. The community always writes Clone, Copy in that order. The compiler requires Clone to be present before Copy can be derived. Writing them in reverse order triggers a compilation error. Following the Clone, Copy order prevents that mistake and signals intent to other developers.
Keep your derives alphabetized or grouped by safety level. The Clone, Copy pair is a recognized unit in the ecosystem.
When copying becomes expensive
Calling .clone() on a String allocates new heap memory and copies every byte. Calling it on a Vec allocates a new array and copies every element. If you call it inside a tight loop, your program will crawl. The garbage collector in other languages hides this cost. Rust exposes it.
This exposure is a feature. It forces you to ask whether you actually need a deep copy. Often you do not. You might only need to read the data. In those cases, pass a reference (&String or &Vec). References are Copy. They duplicate a pointer and a lifetime marker. The cost is negligible.
If you need shared ownership without deep copying, reach for Rc<T> or Arc<T>. Cloning an Rc increments a reference counter. It does not touch the underlying data. The convention is to write Rc::clone(&data) instead of data.clone(). The explicit form makes it obvious that you are cloning the wrapper, not the payload.
Measure before you optimize. Prematurely avoiding .clone() leads to convoluted lifetime annotations and borrowed data structures that are harder to maintain. Clone when the data is small or the scope is short. Borrow when the data is large or the scope is long.
Profile your hot paths. Let the numbers dictate your duplication strategy.
Pitfalls and the E0382 error
The most common mistake is assuming that assignment always copies. You write let b = a; and expect both variables to work. The compiler rejects you with E0382 (use of moved value). The error message points to the line where you used a after the assignment. It tells you that a was moved.
Another trap is mixing Copy and non-Copy types in tuples or arrays. A tuple is only Copy if every element is Copy. (String, i32) cannot be copied. (i32, i32) can. The compiler checks each field individually. If one fails, the whole type fails.
Custom structs that hold file handles, database connections, or network sockets cannot be Copy. They implement Drop to close the resource. Bitwise copying would create two handles pointing to the same underlying resource. Closing one would invalidate the other. Rust blocks this at compile time.
If you need to duplicate a resource handle, the type usually provides a dedicated method like try_clone() or try_clone() on TcpStream. These methods negotiate with the operating system to create a new file descriptor. They fail at runtime if the OS refuses. They cannot be Copy because the duplication is not guaranteed to succeed.
Read the error message completely. The compiler tells you exactly which variable moved and where. Follow the chain. Add .clone() or change the assignment to a reference.
Treat E0382 as a design prompt, not a nuisance. It is asking you to clarify data flow.
Choosing your duplication strategy
Use Copy when the type contains only stack data and does not manage external resources. Use Copy for mathematical primitives, configuration flags, and small fixed-size arrays. Use Copy when you want the compiler to duplicate values implicitly during assignment and function calls.
Reach for Clone when the type owns heap memory or manages complex state. Reach for Clone when you need an independent duplicate that can be modified without affecting the original. Reach for Clone when the duplication cost is acceptable for your performance budget.
Pick references when you only need to read the data. Pick Rc<T> or Arc<T> when multiple owners need to share the same allocation. Pick dedicated handle methods like try_clone() when duplicating OS resources.
Avoid blind .clone() calls in performance-critical loops. Avoid forcing Copy on types that own resources. Avoid fighting the compiler with unsafe just to bypass move semantics.
Let the trait system guide your architecture. The duplication strategy you choose today determines how easily you can refactor tomorrow.