Technology
Move vs Copy: The Fundamental Distinction That Governs Every Assignment
Discover how Rust distinguishes between cheap stack-copy types and resource-managing move types, ensuring performance is predictable and explicit.
6 min read
On this page
- Copy Types: Cheap Duplication, Both Variables Valid
- Move Types: Resource Managers, Original Invalidated
- The Copy Trait: Opting In to Cheap Duplication
- Clone: The Explicit Copy for Non-Copy Types
- The Decision: Copy, Move, Clone, or Borrow?
- Broader Implications: Performance by Default
- Practical Applications & Strategic Takeaways
- Our Commitment to Open Knowledge
- Support Our Mission & Get Your Handbook
Here's something that confuses every Rust newcomer exactly once: let y = x; does completely different things depending on what type x is. If x is an i32, both x and y are valid afterward—x was copied. If x is a String, only y is valid—x was moved. Same syntax. Different semantics. Different rules.
This isn't arbitrary. It's a deliberate design choice rooted in the cost of duplication. Copying an i32 costs 4 bytes—a single CPU instruction. Copying a String costs allocating new heap memory, copying potentially megabytes of text, and managing two independent heap allocations. Making the cheap operation implicit (copy) and the expensive operation explicit (clone) ensures that expensive operations never happen accidentally.
This distinction—between types that are cheaply duplicated (Copy types) and types that manage resources (Move types)—is one of Rust's most important design decisions. Understanding it explains why some code compiles and other seemingly identical code doesn't, and it gives you precise control over when your program allocates memory.
At RantAI, the Copy/Move distinction helps our engineers write performance-predictable code. When we see let config = default_config;, we know whether default_config was copied (cheap, both valid) or moved (cheap, only config valid) based entirely on the type. This article, drawn from Chapter 9, Section 9.3.1 of our guide "The Rust Programming Language," explains the distinction and how to use it effectively.
Copy Types: Cheap Duplication, Both Variables Valid#
fn main() {
// All of these types implement Copy
let a: i32 = 42;
let b = a; // COPY: both a and b are valid
println!("a={}, b={}", a, b); // Works!
let x: f64 = 3.14;
let y = x; // COPY
println!("x={}, y={}", x, y); // Both valid
let flag: bool = true;
let flag2 = flag; // COPY
println!("{}, {}", flag, flag2); // Both valid
let ch: char = 'A';
let ch2 = ch; // COPY
println!("{}, {}", ch, ch2); // Both valid
}
Copy types are types where duplication is as cheap as moving—they're small, stack-only values with no heap allocation. Copying them means copying a few bytes on the stack. There's no heap data to worry about, no resource to manage, no cleanup to coordinate.
Types that implement Copy: i8 through i128, u8 through u128, f32, f64, bool, char, usize, isize, tuples of Copy types (up to 12 elements), and arrays of Copy types.
Move Types: Resource Managers, Original Invalidated#
fn main() {
// These types do NOT implement Copy
let s1 = String::from("hello");
let s2 = s1; // MOVE: s1 is invalidated
// println!("{}", s1); // COMPILE ERROR
println!("{}", s2); // Only s2 is valid
let v1 = vec![1, 2, 3];
let v2 = v1; // MOVE
// println!("{:?}", v1); // COMPILE ERROR
println!("{:?}", v2); // Only v2 is valid
let file = std::fs::File::open("data.txt").unwrap();
let file2 = file; // MOVE: file handle transferred
// file.read(...); // COMPILE ERROR: file was moved
}
Move types manage resources—heap memory, file handles, network connections, mutex locks. Duplicating them would mean duplicating the resource management, which is either expensive (heap copying) or impossible (you can't duplicate a file handle meaningfully).
The Copy Trait: Opting In to Cheap Duplication#
#[derive(Copy, Clone, Debug)]
struct Point {
x: f64,
y: f64,
}
#[derive(Copy, Clone, Debug)]
struct Color {
r: u8,
g: u8,
b: u8,
}
fn main() {
let p1 = Point { x: 1.0, y: 2.0 };
let p2 = p1; // COPY: both valid
println!("p1={:?}, p2={:?}", p1, p2);
let c1 = Color { r: 255, g: 128, b: 0 };
let c2 = c1; // COPY: both valid
println!("c1={:?}, c2={:?}", c1, c2);
}
You can make your own types Copy by deriving the trait—but only if all fields are also Copy. A struct containing a String or Vec cannot be Copy because those types manage heap resources.
// This WON'T compile:
// #[derive(Copy, Clone)]
// struct User {
// name: String, // String is not Copy!
// age: u32,
// }
// This will:
#[derive(Copy, Clone)]
struct Dimensions {
width: f64, // f64 is Copy
height: f64, // f64 is Copy
}
Clone: The Explicit Copy for Non-Copy Types#
fn main() {
let s1 = String::from("hello");
let s2 = s1.clone(); // Explicit deep copy
println!("s1={}, s2={}", s1, s2); // Both valid
let v1 = vec![1, 2, 3];
let v2 = v1.clone(); // Explicit deep copy
println!("v1={:?}, v2={:?}", v1, v2); // Both valid
}
.clone() creates an independent deep copy. It's explicit because it might be expensive—you see every clone in the code because you wrote .clone(). This visibility is a feature: in code review, .clone() calls are spots to examine for potential optimization.
The Decision: Copy, Move, Clone, or Borrow?#
Situation Use Cost Small stack-only data (i32, f64, bool) Copy (automatic) Negligible Need the data in two places, type is Copy Copy (automatic) Negligible Transferring data to new owner Move (automatic) 24 bytes Need independent copy of heap data .clone() (explicit) Proportional to size Need temporary read access &T borrow 8 bytes (pointer) Need temporary write access &mut T borrow 8 bytes (pointer)
Broader Implications: Performance by Default#
At RantAI, the Copy/Move distinction means our engineers never accidentally copy large data structures. A Vec<f64> with a million elements can't be silently duplicated—it has to be explicitly .clone()'d or moved. This makes performance characteristics visible in the code, not hidden behind implicit behavior.
Practical Applications & Strategic Takeaways#
For newcomers: If the compiler says "value moved," you're working with a Move type. Either clone it, borrow it, or restructure your code so the move happens last.
For API designers: Make small, data-only structs Copy (derive it). Keep structs with heap resources as Move types. The Copy/Move distinction is part of your API's performance contract.
For performance engineers: Every .clone() in your code is a potential optimization target. Profile to find the expensive ones.
Our Commitment to Open Knowledge#
RantAI is committed to open education. Move vs Copy is covered in Chapter 9, Section 9.3.1 of our guide, "The Rust Programming Language," freely available online.
Explore these concepts further: https://trpl.rantai.dev
Support Our Mission & Get Your Handbook#
Get the Handbook on Amazon KDP: https://www.amazon.com/dp/B0DHCMD3F2
Get the Handbook on Google Play Books: https://play.google.com/store/books/details?id=INwfEQAAQBAJ
Do you derive Copy on your small structs? What's your rule for deciding? Share your patterns below!
#RustLang #CopyTrait #MoveSemantics #Clone #RantAI #LearnRust #TypeSystem #Performance #MemoryManagement #SystemsProgramming
Next article
References and Lifetimes: Proving Your Pointers Are Valid at Compile Time