Right, let’s talk about copying. You’ve probably already done this without thinking: let x = 5; let y = x;. And then you used both x and y and the universe didn’t implode. That’s because integers are Copy. But then you tried the same thing with a String and got ruthlessly yelled at by the borrow checker. What gives?

The difference is whether the type has a destructive operation when it goes out of scope. For types like String or Vec, that operation is freeing the heap memory they own. Letting two variables think they own and are therefore responsible for freeing that same chunk of memory is a one-way ticket to a segmentation fault. So Rust prevents it through the ownership rules. A Copy type, by contrast, is so boring, so self-contained, that creating a perfect, independent duplicate of its bits is trivial and safe. There’s no heap allocation to worry about, no special resources to clean up. It’s just a bag of bits that can be trivially duplicated.

The Primitives Are All Copy (Obviously)

This one’s easy. All the basic building blocks are Copy. If it lives entirely on the stack and has no ownership semantics, it’s a shoo-in.

// All of these are Copy. Go wild.
let a: i32 = 42;
let b: char = 'πŸ¦€';
let c: bool = true;
let d: f64 = 3.14159;

let a_copy = a; // `a` is still perfectly valid here.
let b_copy = b; // So is `b`.
println!("{a}, {a_copy}, {b}, {b_copy}"); // No problems.

This includes arrays of Copy types, because an array itself is just a contiguous block on the stack.

let numbers = [1, 2, 3, 4, 5];
let numbers_copy = numbers; // The whole array is copied.
println!("Original: {:?}", numbers); // Still works!

Tuples: It Depends on the Company They Keep

Tuples are a bit of a gossip. They’re only Copy if every single one of their elements is also Copy. The compiler is judgy like that; it won’t associate with a tuple that’s hanging out with non-Copy types.

// This tuple is Copy because all its members are Copy.
let copy_tuple: (i32, char, bool) = (10, 'z', false);
let copied_tuple = copy_tuple;
println!("Original tuple: {:?}", copy_tuple); // Still fine.

// This tuple is NOT Copy because it contains a String.
let non_copy_tuple: (i32, String) = (42, String::from("oops"));
let copied_non_copy = non_copy_tuple; // This is a MOVE.
// println!("{:?}", non_copy_tuple); // ERROR! Value borrowed after move.

The moment you put a String in there, the whole tuple becomes a mobile home of ownership. Assigning it to a new variable moves the entire structure, invalidating the original. The designers weren’t being difficult here; it’s the only logical choice. A tuple’s Copy implementation can’t magically deep-copy a heap-allocated Stringβ€”that would be wildly unsafe and inefficient.

References: They’re Just Borrowing the Concept

Here’s a subtle one that often gets missed: shared references (&T) are Copy. Think about it. What does a &T represent? It’s a permission slip to look at, but not mutate or destroy, a value. Handing out a copy of that permission slip is perfectly safe. You’re not duplicating the data itself, just the pointer to it and the promise to play nice.

let s = String::from("A string"); // Not Copy.
let borrowed_s: &String = &s; // This is a shared reference.

// We can copy the reference all day long. The original `s` is still owned
// by... well, `s`. And all these references are just pointing at it.
let ref1 = borrowed_s;
let ref2 = borrowed_s;
let ref3 = ref1;

println!("{s}, {ref1}, {ref2}, {ref3}"); // All good.

Mutable references (&mut T), on the other hand, are not Copy. This is brilliantly obvious in hindsight. If you could copy an &mut T, you’d end up with multiple exclusive references to the same data, violating Rust’s core axiom of either one mutable reference or multiple immutable references, but never both. The whole system would collapse. So the designers, wisely, made sure you can’t accidentally duplicate your one golden ticket of mutability.

The key takeaway? The Copy trait is a marker of triviality. It tells Rust, “Relax, this type is so simple that a bit-for-bit copy is all you need for a completely independent instance.” It’s a promise your type makes to the compiler. And once you understand what that promise entails, the rules around it stop feeling arbitrary and start feeling like the only sane way to prevent chaos.