Copy: The promise that data is cheap to duplicate
You write let x = 42;. You pass x to a function. You print x immediately after. It works. You write let s = String::from("hello");. You pass s to a function. You print s immediately after. The compiler rejects you with E0382 (use of moved value).
The difference isn't magic. It's the Copy trait.
Rust's ownership system moves values by default. When you assign a value or pass it to a function, the original binding becomes invalid. The new owner is responsible for cleanup. Copy is an exception to this rule. It tells the compiler that a bitwise copy of the value is safe and cheap. When a type implements Copy, assignment and function calls duplicate the data instead of moving it. The original value remains valid.
The sticky note vs the deed
Think of values as physical objects. Some values are like a sticky note with a number written on it. You can photocopy the sticky note instantly. The original stays on the wall. You now have two identical notes. Copying costs almost nothing.
Other values are like a deed to a house. The deed is a piece of paper, but it represents something huge. If you photocopy the deed, you have two pieces of paper claiming the same house. That causes chaos. Rust treats String like the deed. It owns heap memory. Copying a String bitwise would duplicate the pointer to the heap data. Both copies would try to free the same memory when they drop. That's a double-free bug.
Copy types are the sticky notes. They contain all their data directly. No pointers to external resources. No cleanup logic. Copying them is just moving bytes.
What the compiler does with Copy
Copy is a marker trait. It has no methods. It changes the semantics of assignment. When you write let b = a; for a Copy type, the compiler generates a bitwise copy. The original a stays alive.
Copy implies Clone. If a type is Copy, it must also implement Clone. The clone() method on a Copy type just performs the same bitwise copy. You can call .clone() on a Copy type, but it's redundant. The compiler optimizes it away.
fn main() {
let n = 42;
// n is Copy. Assignment creates a bitwise copy.
// n remains valid after this line.
let m = n;
// Both bindings are independent.
// Changing m does not affect n.
let m = 100;
println!("n is {n}, m is {m}");
}
Convention aside: always derive Clone alongside Copy. Write #[derive(Copy, Clone)]. The compiler allows Copy without Clone in rare cases, but the community standard is strict. If you can copy, you can clone. They go together. Omitting Clone confuses readers and breaks generic code that expects Clone.
The default list
Rust implements Copy for all primitive scalar types. These types live entirely on the stack. Their size is known at compile time. Copying them is a single CPU instruction.
The following types are Copy:
- Integers:
i8,i16,i32,i64,i128,isize. - Unsigned integers:
u8,u16,u32,u64,u128,usize. - Floats:
f32,f64. - Boolean:
bool. - Character:
char. - Raw pointers:
*const T,*mut T.
Tuples and arrays are Copy if every element inside them is Copy. The compiler checks recursively. If a tuple contains a String, the tuple is not Copy. If an array contains i32 values, the array is Copy.
fn main() {
// Tuple of primitives. All elements are Copy.
// The tuple itself is Copy.
let t = (42, true, 3.14);
let t2 = t;
println!("{:?}", t); // t is still valid
// Array of primitives. Fixed size, all Copy.
// The array is Copy.
let arr = [1, 2, 3, 4];
let arr2 = arr;
println!("{:?}", arr); // arr is still valid
// Tuple with a String. String is not Copy.
// The tuple is not Copy.
// let bad = (42, String::from("no"));
// let bad2 = bad; // Error: bad was moved
}
Deriving Copy for structs
You can make your own types Copy using the derive macro. The compiler checks every field. If all fields are Copy, the struct is Copy. If any field is not Copy, the derive fails.
#[derive(Copy, Clone, Debug)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let p1 = Point { x: 10, y: 20 };
let p2 = p1; // Bitwise copy. p1 remains valid.
// Both points are independent.
println!("p1: {:?}", p1);
println!("p2: {:?}", p2);
}
If you add a field that isn't Copy, the derive breaks. The compiler emits an error explaining which field prevents Copy.
#[derive(Copy, Clone)] // Error!
struct BadStruct {
x: i32,
name: String, // String is not Copy
}
The error points to the String field. String owns heap memory. Copying it bitwise would duplicate the pointer. Rust prevents this. You must remove Copy from the derive, or change the field to a type that is Copy.
References are always Copy
This trips up beginners. References are Copy. &T implements Copy for any T. Even &String is Copy.
A reference is just a pointer and a lifetime. Copying a pointer is safe. The lifetime ensures the reference doesn't outlive the data. You can pass a reference to multiple functions. You can assign it multiple times. The compiler tracks borrows, but the reference value itself copies freely.
fn main() {
let s = String::from("data");
let r1 = &s;
let r2 = r1; // Copy! r1 remains valid.
println!("{r1}, {r2}");
}
This is why borrowing works. You can pass &s to a function and still use s. The function receives a copy of the reference. The reference points to s. No ownership transfers.
The enemy of Copy: Drop
If a type implements Drop, it cannot be Copy. Drop defines cleanup logic that runs when the value goes out of scope. If you copy a value with Drop, you create two values. When both go out of scope, Drop runs twice. That's double-free or double-close. Unsafe.
The compiler enforces this. You cannot derive Copy on a type with a Drop implementation. The compiler rejects it with E0184 (the trait Copy may not be implemented for this type).
struct NoCopy {
data: String,
}
impl Drop for NoCopy {
fn drop(&mut self) {
// Cleanup logic runs when NoCopy is dropped.
// If NoCopy were Copy, this would run twice.
println!("Cleaning up {}", self.data);
}
}
// #[derive(Copy, Clone)] // Error: E0184
// struct NoCopy { ... }
Custom types that manage resources, file handles, or locks usually implement Drop. These types are never Copy. They move. You must clone them explicitly if you need duplication, or share them via references or smart pointers.
Realistic example: Configuration
Configuration structs are prime candidates for Copy. They hold small settings. No heap data. No cleanup. Passing them by value is ergonomic. You don't need references. You don't need clones.
#[derive(Copy, Clone, Debug)]
struct Config {
port: u16,
debug: bool,
timeout_ms: u32,
}
fn start_server(c: Config) {
// c is copied into this function.
// The caller still has c.
println!("Starting server on port {}", c.port);
}
fn log_config(c: Config) {
// c is copied again.
println!("Config: {:?}", c);
}
fn main() {
let cfg = Config {
port: 8080,
debug: true,
timeout_ms: 5000,
};
start_server(cfg);
log_config(cfg); // cfg is still valid!
println!("Main still has cfg: {:?}", cfg);
}
This code passes cfg by value three times. Each call copies the struct. The copies are tiny. The CPU handles them in registers. There's no indirection. No heap allocation. No borrow checker complexity.
If Config grew to include a Vec<String> of allowed hosts, it would lose Copy. You'd have to pass &Config everywhere, or clone explicitly. For small configs, Copy removes friction.
If your type is small and has no cleanup logic, make it Copy. It removes a layer of indirection and simplifies the API.
Pitfalls and errors
You'll hit E0382 (use of moved value) when you try to use a value after passing it to a function that takes ownership. The fix depends on the type. If the type is Copy, the error shouldn't happen. If you see E0382 on a primitive, check for typos or shadowing. If you see it on a struct, the struct isn't Copy.
Generic code often requires T: Copy. If a function takes T and passes it to a callback multiple times, or stores T and returns it, T must be Copy. If you pass a Vec to such a function, you get E0277 (trait bound not satisfied). Vec is not Copy. You must pass a reference, or change the function signature to accept &T.
fn apply_twice<T: Copy>(t: T, f: impl Fn(T)) {
f(t);
f(t); // t must be Copy to use it twice
}
fn main() {
apply_twice(42, |n| println!("{}", n)); // Works: i32 is Copy
// apply_twice(vec![1, 2], |v| println!("{:?}", v)); // Error: E0277
}
Trust the compiler on E0382. It's protecting you from double-free. If you need the value after passing it, either make the type Copy, pass a reference, or clone explicitly.
Decision matrix
Use Copy when your type is a small, stack-only value with no destructor. Integers, floats, booleans, small structs of primitives. The data is the value. Copying is a bitwise operation.
Use Clone when your type owns heap data or has complex state that requires explicit duplication logic. String, Vec, custom types with Drop. Cloning allocates new memory and copies the contents.
Use references (&T) when you want to read data without copying or taking ownership. References are always Copy, so they propagate freely. Use references for large data to avoid allocation overhead.
Use Box<T> or Rc<T> when you need heap allocation or shared ownership. These smart pointers are never Copy. They manage memory or reference counts. Copying them would break their invariants.
Copy is a contract. If your type has cleanup logic, you broke the contract.