As you learn more about Rust programming language, you find out functionalities that seem to work the same, when in reality they differ in subtle ways. This is the case for the Copy
and Clone
traits. This article will explain each trait and show you what makes each different from the otehr.
In Rust, the Copy
and Clone
traits main function is to generate duplicate values. The difference is that Copy
implicitly generates duplicates off of the bits of an existing value, and Clone
explicitly generates deep copies of an existing value, often resulting in a more expensive and less performant operation that duplicating values via the Copy
trait.
This post will explain how the Copy
and Clone
traits work, how you can implement them when using custom types, and display a comparison table between these two traits to give you a better understanding of the differences and similarities between the two.
Table of Contents
How The Copy
Trait Works
As previously mentioned, the Copy
trait generates an implicit duplicate of a value by copying its bits.
What does this mean?
Copying 0s and 1s
Information is stored in bits and bytes. A byte is a collection of 8 bits and a bit is either a 0 or a 1. Every time you have a value, whether it is a boolean, a number, a string, etc, the value is stored in unique byte configuration representing that value.
In other words, if you have the values, such as,
true
false
1
-5
"hello"
to name a few, each value has a collection of bits that denotes their value.
Hence, when you generate a duplicate using the Copy
trait, what happens behind the scenes is copying the collection of 0s and 1s of the given value.
Implicit Duplicate
One of the key words you see in the definition of the Copy
trait is the word “implicit”.
The
For more information, check the Rust documentation: Trait core::marker::CopyCopy
trait generates an implicit duplicate of a value by copying its bits
This means, there is no need to trigger a method, .i.e., .copy()
to generate a duplicate value. Meaning, the duplicate happens if you have a regular assignment like:
let duplicate_value = value;
where duplicate_value
variable gets a copy of the values stored in the value
variable.
Not All Rust Values Can Copy
their own values
One of the most important concepts of Rust is Ownership and Borrowing, which provides memory management different from the traditional garbage collector mechanism.
The ownership and borrowing system makes Rust’s standard behavior to “move” the ownership between the two variables. This is referred as “move semantics”. Take a look at the following example:
#[derive(Debug)]
struct Team {}
fn main() {
let my_team = Team {};
let my_duplicate_team = my_team;
println!("my_team = {my_team:?}, my_duplicate_team = {my_duplicate_team:?}");
}
If you try to run the previous code snippet, Rust will throw the following compile error:
error[E0382]: borrow of moved value: my_team
This has to do with Rust’s ownership system. Meaning, my_team
has an instance of Team
. In other words, my_team
is the owner of that particular instance of Team
.
However, whenever my_duplicate_team
was assigned the values of my_team
, what Rust did behind the scenes was to transfer the ownership of the instance of Team
stored in my_team
. Meaning, the new owner of the instance of Team
is my_duplicate_team
.
Since my_team
no longer owns anything, what Rust’s memory management system does is to remove my_team
no matter if you use my_team
later on within the same function, which leads to the error previously described at compile time (error[E0382]: borrow of moved value: my_team
).
Now, if you have the following code,
fn main() {
let number1 = 1;
let number2 = number1;
println!(
"number 1 = {}, number 2 = {}",
number1, number2
);
}
and attempt to run it, Rust will successfully compile the code and print the values in number1
and number2
.
Why didn’t the code fail if number1
transferred ownership to number2
variable for the value of 1
?
This is a good assumption, but in this case there is no transfer of ownership. In this scenario, you are seeing the Copy
trait in action as it generates a duplicate value by copying the bits of the value 1
stored in number1
. This is referred as “copy semantics”.
Wait a second. How can I know when Rust will implicitly generate a duplicate and when it will implicitly transfer ownership?
By default, Rust implements the Copy
trait to certain types of values such as integer numbers, booleans, characters, floating numbers, etc. You can find a list of the types Rust implements the Copy
trait by default in here. Below you will see a list of a few of them:
How come Rust implemented the Copy
trait in those types by default?
Rust implements the Copy
trait in certain types by default as the value generated from those types are the same all the time. Meaning, all integers (12
), floating-point numbers (3.4
), booleans ( true
, false
), and characters ('a'
, 'z'
) have the same value no matter how many times you use them. Hence, the collection of bits of those Copy
able values are the same over time.
Therefore, it is possible to determine what bits to copy to generate a duplicate value. These values have a known fixed size. Fixed-size values are stored on the stack, which is very fast when compared to values stored in the heap. Hence, making the implicit copy a fast and cheap operation of generating duplicate values.
In the next section, you will learn how to implement the Copy
trait for those types that are non-Copy
by default such as custom structs.
How to Implement the Copy
Trait
There are two ways to implement the Copy
trait to a struct that doesn’t implement it by default. You will notice that in order to add the Copy
trait, the Clone
trait must be implemented too.
Use the #[derive]
attribute to add Clone and Copy
The most common way to add trait implementations is via the #[derive]
attribute. To implement the Copy
trait, derive Clone
and Copy
to a given struct.
#[derive(Clone, Copy)]
struct MyObject {
my_number: u8,
}
Manually add Copy and Clone implementations to the Struct
Another option available to copy the bits of a value is by manually implementing Copy and Clone to a given struct.
struct MyOtherObject {
test: u8,
}
impl Copy for MyOtherObject {}
impl Clone for MyOtherObject {
fn clone(&self) -> MyOtherObject {
*self
}
}
How the Clone
Trait Works
Similar to the Copy
trait, the Clone
trait generates a duplicate value. However, the Clone
trait is different from the Copy
trait in the way it generates the copy.
On one hand, the Copy
trait acts as a “shallow” copy. On the other hand, the Clone
trait acts as a “deep” copy. Deep copies are generally considered more expensive than shallow copies.
The Clone
trait is handy to generate duplicates ofvalues that are stored in the heap. As a reminder, values that don’t have a fixed size are stored in the heap. Some examples are String
orVec
type values.
How to Implement the Clone
Trait
The Clone
trait can be implemented in a similar way you implement the Copy
trait.
Use the #[derive]
attribute to add Clone
To implement the Clone
trait, add the Clone
trait using the derive attribute in a given struct.
#[derive(Clone)]
struct MyObject {
my_number: u8,
}
In comparison to the Copy
trait, notice how the Clone
trait doesn’t depend on implementing other traits.
Manually add a Clone implementation to the Struct
To manually add a Clone implementation, use the keyword impl
followed by Clone for <struct>
. Then, within curly braces generate a clone
function that returns a dereferenced value of the current struct. Below is an example of a manual implementation.
struct MyOtherObject {
test: u8,
}
impl Clone for MyOtherObject {
fn clone(&self) -> MyOtherObject {
*self
}
}
Comparison Between Copy
and Clone
Traits
Copy Trait | Clone Trait |
---|---|
Generates a “shallow” copy / implicit duplicate | Generates a “deep” copy / explicit duplicate |
It is faster as it primarily copies the bits of values with known fixed size. | It is typically slower when duplicating values stored in the heap. |
To implement thecore::marker::Copy trait, the type must have implemented the std::clone::Clone trait. | It can be used as long as the type implements the std::clone::Clone trait. |
There .copy() method does not exist. The duplicate is implicitly generated, .i.e, assuming x derives the Copy trait:x = 1; y = x; y gets an duplicate ofx . | There is a .clone() method available to the type T if T derives the Clone trait. For instance, assuming x derives the Clone trait:x = 1; y = x.clone(); y gets a duplicate of x . |
Summary
All in all, this article covered the differences between the Copy
and Clone
traits whose main purpose is to generate duplicate values.
On one hand, the Copy
trait implicitly copies the bits of values with a known fixed size. Hence, there is no need to use a method such as .copy()
(in fact, that method doesn’t exist). On the other hand, to use the Clone
trait, you must explicitly call the .clone()
method to generate a duplicate value.
Did this article help you understand the differences between the Clone
and Copy
trait?
Share your comments by replying on Twitter of Become A Better Programmer or to my personal Twitter account.