Copy Types vs. Move Semantics in Rust

In Rust, some values are moved when assigned or passed to functions, while others are copied. Types like integers and booleans are Copy types, so they stay usable after assignment. Heap-allocated values like String are moved, meaning the original variable becomes invalid.

When you assign one variable to another in Rust, what happens next depends on the type of value. This behavior is called move semantics. For small, simple values like numbers, Rust just copies the data. For complex, heap-allocated types, Rust transfers ownership using a move.

This post explains the difference between Copy types and moved values, and how to know which one Rust will use. If you are not yet familiar with Ownership or Borrowing and References, read those first.

What Does Move Mean in Rust?

When you assign a value like a String to another variable, Rust moves the value:

let name = String::from("Alice");
let user = name;
// name is no longer valid here

Rust transfers ownership of the String from name to user. You cannot use name anymore.

Why? Because a String is stored on the heap, and Rust does not want to copy heap memory unless you ask for it. This move is fast and avoids bugs from double-free errors.

What Is a Copy Type?

Some types in Rust are so simple that copying them is safe and cheap. These include:

  • Integers: i32, u64, etc.
  • Floating-point numbers: f32, f64
  • Booleans: bool
  • Characters: char
  • Tuples of Copy types: (i32, bool), etc.

These types implement a special trait called Copy, so Rust makes a copy of the value during assignment:

let x = 5;
let y = x;

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

No ownership transfer happens here. Both x and y are valid because the value was simply copied.

How to Know if a Type Is Copy

By default:

  • All primitive scalar types are Copy
  • Any type that only contains Copy types is also Copy
  • String, Vec, Box, and most heap-allocated types are not Copy

You can also make your own struct implement Copy, but only if all its fields are Copy.

Example:

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

This allows you to assign and use the struct without moving ownership.

How to Clone Instead of Move

If a type is not Copy but you still want a duplicate, use clone:

let a = String::from("hello");
let b = a.clone(); // deep copy

println!("a: {}, b: {}", a, b);

Cloning is safe but more expensive than copying. You should only use it when needed.

Functions and Copy vs Move

Passing a Copy type to a function does not affect the original:

fn print_value(x: i32) {
    println!("{}", x);
}

let num = 10;
print_value(num);
println!("Still usable: {}", num); // correct

But passing a non-Copy value like a String will move it:

fn say_hello(name: String) {
    println!("Hello, {}", name);
}

let user = String::from("Alice");
say_hello(user);
// println!("{}", user); // invalid here

Use a reference if you want to avoid moving the value.

Summary Table

TypeHeap or StackCopy or MoveStill usable after assignment?
i32, boolStackCopyYes
StringHeapMoveNo
&strReferenceCopyYes
Vec<T>HeapMoveNo

Final Word

Rust uses Copy for small, fixed-size types and Move semantics for heap-allocated data. This keeps memory usage efficient and prevents common bugs. If you need to reuse a moved value, you can clone it or borrow it using references.

Next: Cloning Data in Rust