Skip to main contentMove, Copy, and clone | IoT Worker

Move, Copy, and clone

C developers often read assignment and parameter passing as copying a value:

uint32_t a = 10;
uint32_t b = a;

Struct values can also be copied:

struct Point p1 = {1, 2};
struct Point p2 = p1;

But once a struct contains a pointer, the meaning changes:

struct Buffer {
    uint8_t *data;
    size_t len;
};

struct Buffer b2 = b1; // only copies pointer and length

That is not a deep copy. b1.data and b2.data point to the same allocation. Who frees it? Can both mutate it? Can one still use it after the other frees it? C handles this by convention.

Rust splits this into three concepts:

move   -> ownership transfers; the old value cannot be used
Copy   -> simple implicit copy; the old value remains usable
Clone  -> explicit copy; may allocate or be expensive

Simple Values Are Usually Copy

Integers, booleans, and floats copy on assignment and by value:

let a: u32 = 10;
let b = a;

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

u32 implements Copy. b gets its own value, and a remains usable.

Function parameters work the same way:

fn print_id(id: u32) {
    println!("{id}");
}

let id = 42_u32;
print_id(id);
println!("{id}");

This is close to passing integers by value in C.

If all fields of a struct are Copy, the struct can be Copy too:

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

let p1 = Point { x: 1, y: 2 };
let p2 = p1;
println!("{}", p1.x);

A Copy type should be cheap, direct, and free of resource-ownership ambiguity.

Resource-Owning Values Move by Default

String, Vec<T>, and File own resources. Assignment moves ownership instead of copying the resource:

let a = String::from("device");
let b = a;

// println!("{a}"); // not allowed
println!("{b}");

This is closer to transferring ownership of a pointer-owning C struct than to deep copying it. Rust invalidates the old variable so two values do not both think they own the same resource.

Vec<u8> is the same:

let packet = vec![1_u8, 2, 3];
let moved = packet;

// packet cannot be used
println!("{}", moved.len());

Passing to a function also moves:

fn consume(data: Vec<u8>) {
    println!("{}", data.len());
}

let data = vec![1_u8, 2, 3];
consume(data);
// data cannot be used

If the function only reads, borrow:

fn inspect(data: &[u8]) {
    println!("{}", data.len());
}

let data = vec![1_u8, 2, 3];
inspect(&data);
println!("{}", data.len());

Clone Is Explicit Copying

If you really need two owned values, use clone():

let a = String::from("device");
let b = a.clone();

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

For String, clone() allocates new memory and copies text. For Vec<T>, it copies elements.

let a = vec![1_u8, 2, 3];
let b = a.clone();

This is closer to a C deep copy:

uint8_t *copy = malloc(len);
memcpy(copy, src, len);

Do not use clone() as the default way to silence ownership errors. It is a real operation and may allocate.

How Copy and Clone Relate

Copy is a stronger promise: copying may happen implicitly, and it should behave like copying a simple value.

Clone is explicit:

let b = a.clone();

Every Copy type should also be Clone, so this derive is common:

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct DeviceId(u32);

This newtype only wraps a u32, so it has no resource-ownership ambiguity and is a good Copy candidate.

This type should not be Copy:

struct Packet {
    data: Vec<u8>,
}

If copying Packet only copied the Vec pointer, length, and capacity, two values would point to the same heap allocation. Rust does not allow Vec<u8> to be Copy, so this Packet cannot be automatically Copy.

If you want to copy the packet, make it explicit:

#[derive(Clone)]
struct Packet {
    data: Vec<u8>,
}

let p2 = p1.clone();

Clone Bounds in Function Signatures

Generic code must ask for Clone if it needs to clone a T:

fn duplicate<T: Clone>(value: T) -> (T, T) {
    (value.clone(), value)
}

If only simple copyable values are allowed, ask for Copy:

fn duplicate_copy<T: Copy>(value: T) -> (T, T) {
    (value, value)
}

The bounds mean different things:

T: Copy   -> implicit copying is allowed, suitable for simple values
T: Clone  -> explicit copying is available, possibly with cost

Do not add Clone to generic APIs just for convenience. It becomes a requirement on the caller’s type.

Migrating C Struct Pass-by-Value

C commonly passes small structs by value:

struct Point {
    int x;
    int y;
};

void draw_point(struct Point p);

Rust can make that type Copy:

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

fn draw_point(p: Point) {
    println!("{}, {}", p.x, p.y);
}

But if the struct owns heap memory:

struct Config {
    name: String,
}

Passing by value means taking ownership:

fn set_config(config: Config) {
    // takes config
}

If the function only reads it, take a borrow:

fn print_config(config: &Config) {
    println!("{}", config.name);
}

This is one of the core API differences: pass-by-value is not always a cheap copy. It may mean ownership transfer.

What to Keep

The first C-to-Rust mapping:

integer / small POD by value      -> Copy
assignment of malloc-owning struct -> move by default, avoids double free
manual deep copy                  -> Clone / clone()
function only reads               -> borrow; do not clone
generic code needs copying         -> T: Clone or T: Copy
Vec/String/File resources          -> move by default, not Copy

move, Copy, and Clone answer one question: did this assignment or parameter passing transfer ownership, cheaply copy a simple value, or explicitly duplicate a resource?

Continue Reading