Rust - Copy vs Clone vs Dupe
By Tomasz Kuczma
One of the features I love in Rust is very explicit copying.
Every potentially expensive copy (clone) is clearly visible and can be easily caught during code review even though a small piece of code has been changed.
That is probably one of the biggest advantages of Rust over C++.
Nevertheless, more experienced Rust programmers know that it is not always easy to judge if some clone is expensive or not.
In this article, I would like to present Facebook’s solution for that problem - Dupe
trait from Gazeebo crate
.
Copy vs Clone
In Rust, there is a definitive difference between copy and clone.
Clone
is a standard trait designed to explicitly duplicate an object T
. Technically, it does not mean copying a “huge” amount of memory as some people think e.g.
let foo = vec![1, 2, 3];
let foo2 = foo.clone(); // explicit duplication of an object
let bar = Rc::new(vec![1, 2, 3]);
let bar2 = bar.clone(); // explicit duplication of an object
In both cases (foo
and bar
), we duplicated an object. foo.clone()
performed a full clone of a vector, so underlying vector’s array was copied on the heap (1 malloc
call + O(n)
int copies). bar.clone()
performed a clone of an Rc
(reference counting pointer), so the cost of doing so is much lower (1 pointer address copy on stack + 1 usize
increment).
Of course, there is corresponding cost of desturction (foo
- is 1 free
call, bar
is 1 usize
decrement).
Copy
on the other hand is a marker trait - so trait that is used only for marking a type. When the struct is marked as Copy
then the compiler knows that it can be duplicated simply by copying bits (like via memcpy
call). Note that this works well with primitive types like i32
or usize
or would even work with simple structs like struct Point2d { x: i32, y: i32}
but would not work with e.g. Rc
where an extra increment must occur on object duplication (hence you need to call Rc::clone
).
Also, when type is marked as Copy
it is copied when it is passed by value instead of being moved.
Dupe
As you can see above, Clone
expresses all the varieties of duplication which might be a little bit problematic as the performance characteristic might vary drastically between types.
This might not sound like a huge problem as long as you memorize all your code base. But what if your colleagues just ask you to approve simple code review like:
let foo2 = foo.clone();
It is impossible to tell what is the cost of that clone without knowing the type of foo
.
Facebook also noticed that problem and introduce its own utility library Gazeebo
that has a new trait to solve that problem - Dupe
.
Dupe
provides dupe()
method that simply calls clone()
, but is only implemented for types for which the cost of copying is low.
This “low” is not well defined but in my opinion, is good enough to use on a wider scale.
Right now, our code would look like this:
let foo2 = foo.dupe();
and I would be sure that it will not cost us much of performance (e.g. it is Rc::dupe
but for sure is not Vec::dupe
as Dupe
is not implemented for Vec
). We could also implement a code review bot to auto-post comments when clone()
is used since we have low-cost dupe()
now.
You can read more about it here
.
I also encourage you to check out other utilities in Gazeebo.
Summary
Rust has a great design feature of distinguishing between simple bit Copy
and duplication of object Clone
which is very explicit.
Performance problems can be easily spotted in the code base and during code review.
However, Clone
can do anything from a simple stack copy of a few bytes in a struct to a heap copy of a huge array.
To highlight or enforce lightweight Clone
, you can use Dupe
which is an explicit way of saying “only light clone”.
Software engineer with a passion. Interested in computer networks and large-scale distributed computing. He loves to optimize and simplify software on various levels of abstraction starting from memory ordering through non-blocking algorithms up to system design and end-user experience. Geek. Linux user.