In this lesson, you will learn why some variables are automatically duplicated instead of moved, and how to identify these "Copy" types.
The Default: Move Semantics
As you’ve learned in previous lessons, Rust defaults to move semantics. When you assign a variable to another, the ownership is transferred, and the original variable becomes unusable. This protects you from double-free errors. However, for tiny, simple data, moving every time can be overkill.
Understanding Copy Types
Rust designates certain types as Copy types. When you assign a Copy type to a new variable, Rust does not perform a "move." Instead, it performs a bitwise copy of the data. Because these types have a known, fixed size and reside entirely on the stack (as discussed in Lesson 20), duplicating them is computationally cheap and safe.
Most primitive types are Copy. This includes:
- Integers (
i32,u64, etc.) - Floating-point numbers (
f32,f64) - Booleans (
bool) - Characters (
char) - Tuples or arrays, provided all their individual elements are also
Copytypes.
Seeing it in Action
Consider this snippet:
let x = 5;
let y = x; // x is copied, not moved!
println!("x is: {}, y is: {}", x, y);
If x were a String (which owns heap memory), let y = x would move the ownership, and the println! would fail because x would no longer be valid. But because 5 is an i32—a Copy type—the value is duplicated. Both x and y remain perfectly valid and usable.
Why not everything is Copy?
Complex types, like String or Vec, manage memory on the heap. If they were Copy by default, assigning them would trigger a "deep copy" of that heap data, which could be slow and memory-intensive without you realizing it. By requiring a move (or an explicit .clone()), Rust ensures you are always aware of when you are performing an expensive operation.
Key Takeaway: Types that implement the Copy trait are duplicated bit-by-bit during assignment, allowing the original variable to remain valid and usable, unlike move-only types.