RustMove, Copy & Clone

Move, Copy & Clone in Rust

Every time you assign a value or pass it to a function in Rust, one of three things happens: the value is moved, copied, or cloned. Understanding which one applies — and why — is essential for writing idiomatic, efficient Rust code.

Move Semantics

A move transfers ownership from one variable to another. After a move, the original variable is considered uninitialised and the compiler will reject any attempt to use it:

RUST
let s1 = String::from("hello");
let s2 = s1; // ownership moves from s1 to s2

println!("{}", s2); // OK — s2 owns the value
println!("{}", s1); // COMPILE ERROR

Moves apply to heap-owning types — types that allocate memory on the heap whose size cannot be known at compile time. Examples: String, Vec<T>, Box<T>, HashMap<K, V>.

When a move happens, only the small stack header (pointer, length, capacity) is copied. The heap data stays in place. The original variable is invalidated to ensure only one owner calls drop on that heap memory.

The Copy Trait

Types that implement the Copy trait are duplicated bitwise on assignment, rather than moved. Both the original and the new variable remain valid:

RUST
let x: i32 = 42;
let y = x; // x is COPIED — no move

println!("x = {}, y = {}", x, y); // both are valid

This works because Copy types live entirely on the stack. Their size is fixed and known at compile time, and duplicating them is just a memcopy of a few bytes — cheap enough that Rust does it automatically.

Which Types Implement Copy?
  • All integer types — i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize

  • Floating-point types — f32, f64

  • The boolean type — bool

  • The character type — char

  • Tuples, if every element type is Copy — e.g. (i32, bool) is Copy, but (i32, String) is not

  • Fixed-size arrays, if the element type is Copy — e.g. [i32; 4] is Copy

  • References (&T) are Copy — sharing a borrow is cheap and safe

Note
`Copy` is an opt-in marker trait. The standard library only implements it for types where the bitwise duplicate is semantically identical to the original — no hidden heap allocations, no file handles, no ownership of resources.
Types That Are NOT Copy

Any type that owns heap data does NOT implement Copy. Duplicating the stack header while leaving the heap buffer shared would break ownership rules (both copies would try to free the same memory):

  • String — owns a growable heap buffer

  • Vec<T> — owns a growable heap array

  • Box<T> — owns a heap-allocated value

  • HashMap<K, V> — owns a heap-allocated hash table

  • Any type that implements Drop — if you wrote custom cleanup logic, it cannot be trivially copied

RUST
let v1: Vec<i32> = vec![1, 2, 3];
let v2 = v1; // moved, NOT copied

println!("{:?}", v2); // OK
println!("{:?}", v1); // COMPILE ERROR — v1 was moved
The Clone Trait: Explicit Deep Copy

When you genuinely need a second, independent copy of a heap-owning value, you call .clone(). This performs a deep copy — both the stack header and the heap data are duplicated, producing two fully independent values:

RUST
let s1 = String::from("hello");
let s2 = s1.clone(); // deep copy — new heap allocation, new stack header

println!("s1 = {}", s1); // still valid — s1 owns its own copy
println!("s2 = {}", s2); // s2 owns a separate copy

After .clone(), both s1 and s2 own separate heap allocations. Modifying or dropping one has no effect on the other. When each goes out of scope, its own heap buffer is freed independently.

Warning
`.clone()` can be expensive. Cloning a `Vec` with a million elements allocates a million-element buffer and copies every byte. The explicitness is intentional — Rust makes you write `.clone()` so that performance costs are visible in the source code, not hidden by an implicit copy.
Copy vs Clone: The Key Differences

Property

Copy

Clone

How triggered

Implicit — happens on assignment, function call

Explicit — you must call .clone()

What is duplicated

Stack bits only (shallow copy)

Stack bits + heap data (deep copy)

Cost

Negligible — just a few bytes

Potentially expensive — proportional to data size

Requires heap allocation

No

Usually yes (for heap-owning types)

Can a type implement both?

Yes — Copy requires Clone

Clone alone is common; Copy is stricter

Can a type implement Drop?

No — Copy and Drop are mutually exclusive

Yes — Clone and Drop are compatible

Implementing Copy on Your Own Types

You can derive Copy (and the required Clone) on your own structs, but only if every field is also Copy and the type does not implement Drop:

RUST
#[derive(Copy, Clone, Debug)]
struct Point {
    x: f64,
    y: f64,
}

let p1 = Point { x: 1.0, y: 2.0 };
let p2 = p1; // copied — p1 still valid

println!("p1 = {:?}", p1);
println!("p2 = {:?}", p2);

If you add a String field to Point, deriving Copy will fail because String is not Copy:

RUST
#[derive(Copy, Clone)]
struct BadPoint {
    x: f64,
    label: String, // String does not implement Copy
}
Clone in Trait Bounds

In generic code, T: Clone is a very common bound — you are saying "I may need to clone a T inside this function." T: Copy is a stricter requirement and is used less often:

RUST
// Works for any type that can be explicitly cloned
fn duplicate<T: Clone>(item: T) -> (T, T) {
    (item.clone(), item)
}

// Works only for types that are automatically copied
fn copy_both<T: Copy>(item: T) -> (T, T) {
    (item, item) // no .clone() needed — copied implicitly
}

let pair = duplicate(String::from("hi")); // String: Clone but not Copy
println!("{:?}", pair); // ("hi", "hi")

let nums = copy_both(42i32); // i32: both Copy and Clone
println!("{:?}", nums); // (42, 42)
When to clone() vs Restructure to Avoid It

.clone() is the right tool when you genuinely need two independent copies of data. But it is often a symptom of an ownership design issue that can be solved without extra allocation:

  • Use borrowing first. If a function only needs to read data, pass &str instead of String, or &[T] instead of Vec<T>. No clone needed.

  • Use references in structs. A struct that borrows its data (&str) is cheaper than one that owns a clone (String) — but requires lifetime annotations.

  • Use Rc<T> or Arc<T> to share ownership. When multiple parts of your program need to read the same data and you can't use references, a reference-counted pointer lets you clone a cheap pointer rather than the underlying data.

  • Accept the clone. In non-hot-path code — startup, config parsing, error handling — clarity is worth more than saving an allocation. Do not optimise prematurely.

Tip
Profile before optimising clones. In many programs, the clones that feel expensive in theory contribute less than 1% to actual runtime. Fix the ones the profiler confirms, not the ones that look suspicious.
Rc and Arc: Sharing Without Cloning Data

Rc<T> (single-threaded reference counting) and Arc<T> (atomic, thread-safe reference counting) let multiple variables share ownership of the same heap value. Cloning an Rc or Arc just increments a counter — it does NOT copy the inner data:

RUST
use std::rc::Rc;

let data = Rc::new(vec![1, 2, 3]); // one heap allocation
let clone_a = Rc::clone(&data);    // increments ref count — no vec copy
let clone_b = Rc::clone(&data);    // same

println!("count = {}", Rc::strong_count(&data)); // 3
println!("{:?}", clone_a);

When the last Rc pointing to the data is dropped, the inner value is freed. Use Arc instead of Rc when you need to share data across threads.

Common Types: Copy, Clone, or Neither?

Type

Copy?

Clone?

Notes

i32, u64, f32, …

Yes

Yes

All numeric primitives

bool

Yes

Yes

Single byte on stack

char

Yes

Yes

Unicode scalar value (4 bytes)

(i32, bool)

Yes

Yes

Tuple of Copy types

[u8; 8]

Yes

Yes

Fixed array of Copy type

&T

Yes

Yes

Shared references are always Copy

String

No

Yes

Owns heap buffer; clone is deep copy

Vec<T>

No

Yes

Clone duplicates entire buffer

Box<T>

No

Yes (if T: Clone)

Unique heap pointer

HashMap<K,V>

No

Yes (if K,V: Clone)

Clone copies all entries

File, TcpStream

No

No

OS resources; cannot be duplicated

MutexGuard

No

No

Must not be copied or cloned

Move Semantics with Functions

The same rules apply when passing values to and returning values from functions. Heap types are moved; Copy types are copied:

RUST
fn print_string(s: String) {
    println!("{}", s);
} // s is dropped here

fn double(n: i32) -> i32 {
    n * 2
}

fn main() {
    let owned = String::from("moved into function");
    print_string(owned);
    // println!("{}", owned); // ERROR — owned was moved

    let num = 21;
    let result = double(num);
    println!("num={}, result={}", num, result); // num is still valid — i32 is Copy
}
Note
Move, Copy, and Clone are not just language rules — they are a design tool. When you see a type that is `Copy`, you know it is cheap and safe to pass around freely. When you see `.clone()`, you know a real allocation is happening. When you see a move, you know ownership is being transferred and the old variable is gone. Reading Rust code becomes easier once you recognise these signals at a glance.