Move Semantics & Copy
Assignment and passing by value either move (transfer ownership) or copy (duplicate bits) depending on the type. Clone provides explicit duplication when needed.
Recipe
#[derive(Clone, Debug)]
struct Config { host: String, port: u16 }
fn main() {
let port: u16 = 8080; // Copy
let p2 = port;
let cfg = Config { host: "localhost".into(), port };
let cfg2 = cfg.clone(); // explicit deep copy
println!("{port} {:?} {:?}", cfg, cfg2);
}When to reach for this: Deciding whether a type should be Copy, when to clone(), and designing APIs that move vs borrow.
Working Example
#[derive(Copy, Clone, Debug)]
struct Point { x: i32, y: i32 }
fn translate(p: Point, dx: i32) -> Point {
Point { x: p.x + dx, y: p.y }
}
fn main() {
let origin = Point { x: 0, y: 0 };
let moved = origin; // Copy - both valid
let shifted = translate(moved, 5);
println!("{origin:?} {shifted:?}");
}What this demonstrates:
PointisCopybecause all fields areCopyoriginremains usable after assignment- Small stack-only aggregates are good
Copycandidates Stringcannot beCopy(heap ownership)
Deep Dive
Move Semantics
- Non-
Copyassignment invalidates the source binding - Moves are cheap for heap types (pointer copy)
- Function parameters by value take ownership unless borrowed
Vec,String,HashMapalways move on assignment
Copy Requirements
// All fields Copy, no Drop impl (usually)
#[derive(Copy, Clone)]
struct Flags(u32);Types with custom Drop generally cannot be Copy.
Clone vs Copy
| Trait | When | Effect |
|---|---|---|
Copy | Implicit on assignment | Bitwise duplicate |
Clone | Explicit .clone() | User-defined duplication |
Clone on Collections
let v1 = vec![1, 2];
let v2 = v1.clone(); // clones elements if T: CloneGotchas
- Deriving
Copyon struct withString- Compile error. Fix: RemoveCopyor use&str/Arc<str>if sharing needed. - Cloning in loops - Hides performance issues. Fix: Restructure to borrow or reuse buffers.
- Assuming assignment copies
Vec- It moves. Fix:.clone()explicitly when duplicate needed. Copy+Dropconflict - Custom drop means move semantics matter. Fix: Do not implementCopy.- Partial move then use struct - Moved fields invalidate whole struct use in some cases. Fix: Destructure or borrow fields.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
Borrow &T | Read-only shared access | Caller must retain ownership for return |
Rc/Arc | Shared ownership without deep clone | Single owner is clearer |
Cow<'a, T> | Sometimes borrow, sometimes own | Always one mode |
| Move only | Transfer is intended | Callers need to keep using value |
FAQs
Is move a memcpy?
For Copy types, yes. For heap types, move copies the stack metadata (pointer, len, cap), not heap data.
Can I implement Copy manually?
Yes with unsafe impl Copy, but derive is standard. All fields must already be Copy.
Does clone always deep-copy?
Clone semantics are type-defined. Arc::clone only increments refcount.
Why is i32 Copy but String not?
i32 is entirely on stack with no ownership. String owns heap allocation.
What about arrays?
[T; N] is Copy when T: Copy. Large arrays are Copy but expensive to bitwise copy.
Move and partial initialization?
Use MaybeUninit in unsafe patterns. Safe code uses fully initialized values.
Does return move the value?
Yes for non-Copy types. Optimizer may elide copies in practice (NRVO).
Can enums be Copy?
Yes if all variants' payloads are Copy.
What is `#[derive(Clone)]` cost?
Derive recurses fields. Heap fields clone their contents.
Move in async tasks?
Owned data moves into futures. Send bounds govern cross-thread moves.
Related
- Ownership Basics - single-owner rule
- Borrowing & References - avoid moves
- Rc & Arc - shared ownership
- Cow - clone on demand
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.