Who moved my value?

How do fixed values behave and how can we mimic it

Created on April 29, 2023.

When we talked about ownership in Rust, we’ve excluded values who’s size are known at compile time, but what happens to a value that is stored on the Stack:

let mut a: i32 = 2;

// Here the value is copied
let copy_of_a: i32 = a;

a = 3;

// 🖨️ Amount copied: 2 🖨️
println!("Amount copied: {}", copy_of_a);

The 32-bits integer (8 bytes) is copied when we assign it to the variable copy_of_a. The same would have happened if we passed it to a function as a parameter.

Memory layout

This is basically the reason why useEffect in React has a dependency array, the function is memoized and the value is enclosed/copied inside the scope!

Point struct

What if we wanted to create a Point type that behaves like a value?

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

fn main() {
    let mut point = Point { x: 0, y: 0 };

    // Here the ownership will be transferred, aka a move
    let point_is_moved = point;

    point.x = 5; // 💥 Compilation Error 💥
}

Compiler’s output

✦ ▶ cargo -q run
error[E0382]: assign to part of moved value: `point`
 --> src/main.rs:9:5
  |
4 |     let mut point = Point { x: 0, y: 0 };
  |         --------- move occurs because `point` has type `Point`,
                      which does not implement the `Copy` trait
...
7 |     let point_is_moved = point;
  |                          ----- value moved here
8 |
9 |     point.x = 5;
  |     ^^^^^^^^^^^ value partially assigned here after move

Clone

If we look at other types in rust we’ll notice that some of them have a Clone method (String), we could implement that for our type:

fn main() {
    let mut point = Point { x: 0, y: 0 };

    // we clone the value
    let point_is_moved = point.clone();

    point.x = 5;

    // 🖨️ Point copied: { x: 0, y: 0 } 🖨️
    println!("Point copied: {:?}", point_is_moved);
}

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

impl Clone for Point {
    fn clone(&self) -> Self {
        Point {
            x: self.x,
            y: self.y,
        }
    }
}

Implementing the Clone trait is a very explicit way of create a copy, which may or may not be expensive.

We can also derive from the Clone trait 💪:

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

Copy

But what we said we really wanted to mimic the copy behavior of for instance i32? That is the beauty of the Rust language ❤️, there is actually a supertrait of Clone which can be derived from :

fn main() {
    let mut point = Point { x: 0, y: 0 };

    // we copy the value like `i32`
    let point_is_moved = point;

    point.x = 5;

    // 🖨️ Point copied: { x: 0, y: 0 } 🖨️
    println!("Point copied: {:?}", point_is_moved);
}

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

To be able to derive from the Copy trait, all of the underlying values must implement Copy! This is the main difference with Clone, Copy is inexpensive.

Previous

What is ownership?