Copy trait and Clone traits are important concepts in the Rust programming language. Theoretically, they are easy but are hard to apply in real life, especially for new programmers coming from languages like Python, Java, etc. In this post, we will learn about what it means to Copy and Clone in Rust.
Ownership in Rust
Before discussing Copy and Clone traits in rust let’s briefly understand the ownership first. In every programming language, there are ways to manage the computer’s memory while executing. Language-supporting garbage collector regularly looks for no longer used memory is the program executes, while in other languages like C/C++, programmers must explicitly allocate and free the memory.
In Rust, the memory is managed through ownership. Ownership is the set of rules that the compiler checks to manage memory. If any of the ownership rules are violated the program won’t even compile.
Ownership Rules
Because ownership is a new concept for many programmers, it does take some time to get used to, but keeping these rules in mind will help to write code that is safe and efficient.
- In Rust each value has an owner, that is the variable assigned to it.
- There can be only one owner at a time. Although, multiple variables can hold a reference to or borrow the same value – but that value will only have one owner
- When the owner goes out of scope the value will be dropped.
Memory Allocation in Rust (Stack vs Heap)
Rust provides both the Stack and Heap part of memory at runtime. In Rust, it is important to understand memory allocation because how values are stored in memory affects how the language will behave and helps programmers in making certain decisions.
The way value is stored in Heap and Stack is different. Just like stack data structures, Stack stores the value in the order, it gets it and removes the values in the opposite order, i.e., Last In First Out order. In Heap when we want to store data, we request a certain amount of space. The memory allocator finds an empty spot in the heap big enough to store the value, marks it in use, and returns a pointer to the address of that location.
Pushing to the stack is faster than allocating on the heap because the allocator never has to search for a place to store new data.
Values like: u16
, u32
, i32
, and f64
are all stored in a stack because their size is known at compile time.
Dynamic size types like String and Vec are stored in the heap because they can grow or shrink in size and don’t have a fixed size.
Clone trait in Rust
In Rust, some simple types are “implicitly copyable” and when you assign them or pass them as arguments, the receiver will get a copy, leaving the original value in place. For other types, copies must be made explicitly, by implementing the Clone
trait and calling the clone()
method.
The Clone
trait defines the ability to explicitly create a deep copy of an object T. When we call Clone
for type T, it does all the arbitrarily complicated operations required to create a new T.
fn main() { let name = String::fron("IntMain!"); //ownership of value is transferred from name to new_name let new_name = name; //error: name was moved! println!("Old name = {} and new name ={}", name, new_name); }
In the above example according to the ownership rules, the assignment of a name
to new_name
transferred the ownership of the String
instance to new_name
and since there can only be one owner hence name
was dropped. Hence to have a deep copy we need to explicitly call clone()
method.
fn main() { let name = String::fron("IntMain!"); //Deep copy of name is allocated to new_name //explicit duplication of an object let new_name = name.clone(); //Compiles fine with no erros println!("Old name = {} and New name ={}", name, new_name); } }
Due to deep copy both name
and new_name
are free to independently drop their heap buffer.
Note: The Clone
trait doesn’t always create a deep copy. Types are free to implement Clone
in any way they want. For example, Rc
and Arc
only increment a reference count instead of creating a deep copy.
Copy trait in Rust
The Copy
trait in rust defines the ability to implicitly copy an object. The behavior Copy
is not overloadable. It is always a simple bitwise copy. This is available for the types that have a fixed size and are stored entirely on the stack.
fn main() { let num = 987; //new_num is a copy of num let new_num = 987; //Compiles fine with no erros println!("Old num = {} and New num ={}", nun, new_num); } }
The above example is not contradiciting the ownership rules, becuase i32
are Copy type as they are stored in stack and it is easy to copy them, hence value of num
is implicity copied to new_num
. As we can imagine not every type can implement Copy
, the best example is String
which implement Clone
but not Copy
.
The copy type is only applicated to types that are stored in stack, like integers, floats, structs with all their fields Copy
of type.
//This stuct implements Copy trait as //all of its field are of Copy type #[derive(Clone, Copy)] struct copy_and_cloneable{ num1: i32, num2: i32, num3: bool, } //This stuct doen't implements Copy trait as //String is not a Copy type #[derive(Clone, Copy)] stuct cloneable_only{ num1: i32, name: String, //Not a Copy type }
Note: Clone
is a supertrait of Copy
, so everything which implements Copy
must also implement Clone
, hence when a type implements Copy
the Clone
implementation is just returning *self
.
Conclusion
In rust it is easy to understand the difference between Copy and Clone once you understand the Rust memory allocation. Types that can be pushed into stack, implements the Copy trait and types that can be pushed into the heap implements the Clone trait. Since deep copy is expensive hence we need to explicitally invoke clone()
method, otherwise move happens by default. Also, Clone
can perform anything from simple stack copy of few bytes to a heap copy of huge vectors.