If you've been dipping your toes in the awesome Rust language, you must've encountered the clone()
method which is present in almost every object out there to make a deep copy of it. It comes from the implementation of Clone
trait for a struct.
But, you must also have encountered this Copy
trait if you ventured a bit into docs deeper or in an error message. Copy
also allows some type to replicate its instances. So, what's the deal here with Clone
and Copy
traits? That is exactly what this article is about.
I won't delve too deep into ownership here, but this is what we need to know to get the gist of this article:
This ownership model in Rust may seem constraining but it is one of the things that makes Rust a great language. It makes you, the programmer, harder to write code with hideous bugs.
Normally, you don't have to worry about stack or heap memory allocation - it is done automatically by the language. But in Rust, a variable may behave differently depending on whether it is on the stack or heap. Rust strives to be performant because of it.
The general rule is that simple values like numeric types - u16
, i32
, f64
etc. - are stored on the stack as they have known fixed size and it is cheap to copy them. Pointers like &str
(NOT the value it is pointing to!) are also stored on the stack because of their fixed size!
Dynamically sized types (DSTs) like String
and Vec
are stored on the heap. DSTs can be mutated to grow or shrink in size. They don't have a fixed size and it can be very expensive to copy them making them unsuitable for stack location.
Alright! That is enough primer, let's cut to the chase.
Clone
traitThe Clone
trait defines the ability to explicitly deep copy an object. Take an example of a String
struct instance that implements Clone
trait.
// initial owner of string value
let s1 = String::from("hello!");
// ownership of same value transferred to s2
let s2 = s1;
// error! s1 was moved!
println!("{}", s1);
The above example works according to ownership rules of Rust. The assignment of s1
to s2
transfers ownership of the String
instance to s2
, since there can be only one owner of a value at a time in Rust. If you've worked with other statically typed languages you might call this a shallow copy of s1
as s2
now points to the same data as s1
was pointing to, without any re-allocation in memory. Only pointer in the stack was copied, not value in the heap.
In Rust lingo however, it is termed as a move, in the sense that value of s1
is moved into s2
and s1
is no longer valid. Hence any access to s1
will result in an error - which is different from other languages.
Now to make deep copy of the String
you will have to explicitly invoke the clone()
method. When you clone()
a value Rust would replicate both pointer in the stack as well as data at heap.
// owner is s1
let s1 = String::from("hello!");
// deep copy of s1 is allocated to s2
let s2 = s1.clone();
// this is fine!
println!("{}", s1);
This is what clone()
method does - making a deep copy of an instance.
The Copy
trait defines the ability to implicitly copy an object. This is available for the types that have a fixed size and are stored entirely on the stack. u32
is a good example that implements Copy
.
// owner is n1
let n1 = 256;
// n1 is implicitly copied on stack and
// copied value is assigned to n2
let n2 = n1;
// this is fine!
println!("{}", n1);
The above example may seem contradictory to ownership rules at first. But, since u32
are Copy
type, n1
value is implicitly copied into n2
and NOT moved. This is because u32
values are stored on stack and it is cheap to copy them. There is no corresponding copy()
method provided by Copy
trait (unlike Clone
's clone()
). The copy here happens implicitly. Also, there is no difference between a shallow copy or deep copy here.
The Copy
trait can only be annotated to types that are stored on stack. Like integer types or structs that have all their fields of Copy
type. You cannot annotate a type as Copy
if any of its constituent parts cannot be stored on stack.
// This is fine!
#[derive(Clone, Copy)]
struct Demo {
_a: u32, // Copy type ✅
_b: i8, // Copy type ✅
}
// This will NOT compile!
#[derive(Clone, Copy)]
struct Demo {
_a: u32, // a Copy type ✅
_c: String // NOT a Copy type! 🚫
}
The difference between Clone
and Copy
traits become clear when you understand when and what values are stored on stack and heap. Copy
is exclusively for types that can be pushed on the stack and Clone
is for types that can be pushed on the stack as well as on the heap.
Since making a copy of Copy
types is cheap, it is an implicit process. Instead of a move, a Copy
(like u32
) type is copied when assigned to a variable. While making deep copy of a Clone type is expensive. So, to make a deep copy of a Clone
type, you have to explicitly invoke the clone()
method, otherwise, a move happens (copy pointer on stack), which is only a stack operation, hence cheap.
Alright, I hope that clears the confusion between Copy and Clone traits in Rust!
Find me on Twitter @the_nvn!