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