Introduction
This article came from a question I saw on Reddit, and it is a good one to ask, even if it seems dumb at first.
So we all know that Rust has a concept of ownership, and usually whenever a value is not passed by reference, it is moved.
So for instance, if we have a String
and we pass it to a function, it is moved, and we cannot use it anymore in the original scope.
fn move_string(s: String) {println!("{s}");}fn main() {let s = "Hello, World!".to_string();move_string(s);println!("{s}");}
let s = "Hello, World!".to_string();| - move occurs because `s` has type `String`, which does not implement the `Copy` trait7 | move_string(s);| - value moved here8 |9 | println!("{s}");| ^^^ value borrowed here after move
So, whenever we want to reuse the value, we have either to pass it by reference or clone it.
fn move_string(s: String) {println!("{s}");}fn main() {let s = "Hello, World!".to_string();move_string(s.clone());println!("{s}");}
But, some types do not need to be cloned, because they implement the Copy
trait, which means that they can be copied instead of moved, such as integers, booleans, and characters.
fn move_num(n: i32) {println!("{n}");}fn main() {let n = 42i32;move_num(n);println!("{n}");}
So, since a Copy
type is never explicitly moved, the question arises: can we move a Copy
type in Rust?
Is it copied or moved?
If we run the previous code, we can easily check whether n
got moved or copied by checking the address of n
before and after the function call.
fn move_num(n: i32) {let nptr = &n as *const i32;println!("{n}");println!("Number ptr in move_num: 0x{:x}", nptr as usize);}fn main() {let n = 42i32;let nptr = &n as *const i32;println!("Original ptr: 0x{:x}", nptr as usize);move_num(n);println!("{n}");let nptr = &n as *const i32;println!("Number ptr in main: 0x{:x}", nptr as usize);}
If we run this code, we'll see an output like this:
Original ptr: 0x7ffe7747d66442Number ptr in move_num: 0x7ffe7747d5a442Number ptr in main: 0x7ffe7747d664
So we can see, that the move_num
function has a different pointer address for n
, which means that it was copied, not moved, while the original value is still in the main function.
But what if we don't reuse it in the main function? Will it still be copied?
fn move_num(n: i32) {let nptr = &n as *const i32;println!("{n}");println!("Number ptr in move_num: 0x{:x}", nptr as usize);}fn main() {let n = 42i32;let nptr = &n as *const i32;println!("Original ptr: 0x{:x}", nptr as usize);move_num(n);// println!("{n}");// let nptr = &n as *const i32;// println!("Number ptr in main: 0x{:x}", nptr as usize);}
Surprisingly, the value in move_num
is still copied, and the original value is still in the main function.
Original ptr: 0x7ffd588100e442Number ptr in move_num: 0x7ffd58810024
Why Rust doesn't move Copy
types?
The reason why Rust doesn't move Copy
types is that it would be inefficient and pointless to do so.
First, when a value is passed by reference the pointer is copied, not the value itself, so in anycase something is written to memory, but if we instead pass a number by value, it is the same size as a pointer or even smaller (such as i8
or u8
), so copying it is not a big deal.
You could think that for bigger types, such as a [i32; 100]
, it would be more efficient to move it, but in reality, what it does is just copy the pointer to the first element of the array, so it is still efficient.
Second, if Rust were to allow moving Copy
types, it would undermine the point of the Copy trait itself. The distinction between Copy
and non-Copy
types is meant to be clear: Copy types can be freely duplicated without needing to track ownership. Allowing them to be moved would reintroduce ownership tracking for types that are supposed to be trivially copyable, defeating the purpose of the Copy trait.