Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Moves and Clone

Goal of This Episode

Understand Rust’s move semantics — both assignment and passing into a function transfer ownership — and replicate data with Clone.

Concept

Move: Hand It Over and It’s Gone

Last episode we learned traits; now let’s see what ownership looks like in code.

In Rust, when you assign a struct value to another variable, the original variable can no longer be used. This is the “handing over the keychain” from Episode 1:

struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let p1 = Point { x: 1, y: 2 };
    let p2 = p1; // Ownership of p1 moves to p2
    // From here on, p1 can't be used anymore!
}

This behavior is called a move. The Rust compiler checks this at compile time — if you try to use the original variable after a move, the compiler reports an error outright.

Passing into a Function Is Also a Move

It’s not just assignment — passing a value into a function moves it too:

struct Point {
    x: i32,
    y: i32,
}

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

fn main() {
    let p1 = Point { x: 1, y: 2 };
    print_point(p1); // p1 gets moved into the function
    // p1 can't be used anymore!
}

Because a function’s parameter is like a new variable — the value gets “handed” to it.

Clone

If you need to keep the original value and also want a replica, use Clone.

First, add #[derive(Clone)] to your type (throwing in Debug too, why not):

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

fn main() {}

Then replicate the value with .clone():

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

fn main() {
    let p1 = Point { x: 1, y: 2 };
    let p2 = p1.clone();  // Replicate p1; p1 survives
    println!("{:?}", p1); // OK! p1 is still usable
    println!("{:?}", p2); // p2 is an independent replica
}

Recall Episode 1’s analogy: clone means “get a new keychain that works just like the original, while making sure it causes no trouble.” For Point, the new keychain is a complete replica — and that’s exactly what the clone generated by #[derive(Clone)] does: clone every field. Each variable owns its own clone.

Integers Don’t Move?

You may notice integers behave differently:

fn main() {
    let a = 42;
    let b = a;
    println!("{}", a); // This actually works!
}

Why don’t integers move? We’ll answer that next episode.

Example Code

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

fn print_point(p: Point) {
    println!("The function received the point: ({}, {})", p.x, p.y);
}

fn main() {
    let p1 = Point { x: 10, y: 20 };

    // Use clone to make a replica so p1 doesn't get moved away
    let p2 = p1.clone();
    println!("p1 = {:?}", p1);
    println!("p2 = {:?}", p2);

    // Passing into a function moves too, so clone first
    print_point(p1.clone());
    println!("p1 is still here: {:?}", p1);

    // Without cloning, passing it in moves p1 away
    print_point(p1);
    // Uncommenting the line below makes the compiler report an error:
    // println!("p1 is gone: {:?}", p1);
}

Recap

  • let p2 = p1; moves — afterward p1 can’t be used.
  • Passing a value into a function is also a move.
  • #[derive(Clone)] + .clone() calls clone on every field — for a type like Point, that means an independent replica.
  • After a clone, the original variable remains usable.
  • Integers (i32 and friends) don’t move — next episode explains why.