Copy and Clone in Rust

December 28, 2023 in Programming5 minutes

Rust’s Copy trait is one of the earliest things I struggled with when I started learning the language. On its surface, this trait is quite simple, but its implications on how an implementing type can interact with Rust’s ownership model are significant. As I gained more experience, I also encountered the Clone trait frequently as well, and like many others, found it difficult to remember the differences between the two, especially early on. Both are just two of several examples of how Rust is much more explicit about these kinds of things, especially when compared to the languages I’ve historically used.

Copy and Clone are both used for “duplicating stuff” in Rust, but the way it’s actually done, and which are used in what situations are very different.

As usual, keeping the chapter on Rust’s ownership model handy is advisable, as it provides a foundation for just about anything else you’ll learn about the language. I find myself reading and re-reading this often, despite having now worked with the language professionally for a few years.

The Copy trait itself is actually quite simple to understand. To duplicate a value using Copy, it is enough to simply perform a bitwise copy of the value. Copy is a “marker” trait. Meaning it doesn’t have any methods on its own. It does depend on Clone however, which does.

However, not everything can implement Copy. All of the fields of your type must also implement Copy. There are a few common types which cannot implement Copy, so if they’re used anywhere in your own types, they’ll prohibit you from using Copy:

  • String - This points to a buffer stored on the heap. Copy would simply duplicate this pointer, resulting in a double free later. You can see String doesn’t implement Copy by looking at its trait implementations (it has Clone, but not Copy).
  • Vec<T> - A Vec<T> is never Copy, even if it’s elements are Copy, because it is always heap-allocated, and has the same problem as String described above.
  • &mut T - obviously we know we can’t have multiple mutable references, which Copy would create. Multiple shared references are fine though (i.e. &T) as these are already designed to work within the ownership model.

Aside from whether or not a type can implement Copy, it’s still opt-in, meaning you still have to either derive the Copy implementation or make it yourself. See the section below on Clone for more on this.

A nice clue to see why a given type might not be able to implement Copy beyond simply checking its constituent types is to see if it implements Drop. This implies that cleaning up this type from memory is more complex than simply removing its bytes from the stack. The compiler actually prohibits you from implementing Copy if a type also implements Drop - even if the implementation of the latter doesn’t actually do anything:

error[E0184]: the trait `Copy` cannot be implemented for this type; the type has a destructor
 --> src/bin/example.rs:5:15
  |
5 | impl Copy for MyStruct {}
  |               ^^^^^^^^ `Copy` not allowed on types with destructors

In short, anything which:

  • Doesn’t live fully on the stack..
  • Would violate Rust’s ownership model if duplicated. Multiple mutable references, double free potential, etc.

cannot implement Copy, and must rely on the much more permissive Clone. Speaking of which…

Clone

Clone is Copy’s much more liberal sibling trait. When a type needs to be duplicated but for the reasons above abitwise copy won’t suffice, Clone is able to do this.

Interestingly, Clone is actually a super-trait of Copy, so anything that implements Copy also must implement Clone. If you elect to implement this yourself, rather than derive it, you can - it will have a very simple implementation:

struct MyStruct;

impl Copy for MyStruct { }

impl Clone for MyStruct {
    fn clone(&self) -> MyStruct {
        *self
    }
}

There is a minor advantage in certain situations for implementing this yourself instead of deriveing it, which is well-described in the docs.

However, a more complex type can also be duplicated with Clone, even heap-allocated types. This is where techniques like “deep copy” can be done.

Conclusion

[Clone]…differs from Copy in that Copy is implicit and an inexpensive bit-wise copy, while Clone is always explicit and may or may not be expensive. In order to enforce these characteristics, Rust does not allow you to reimplement Copy, but you may reimplement Clone and run arbitrary code.1

By default, variable bindings have “move semantics”. However, if a type implements Copy, it instead has “copy semantics”.2

My interpretation of the above comes in when I run into compilation errors resulting from me trying to use non-Copy types after they’ve been moved to another scope. This forces me to either consider using a reference, or call clone() (provided the type at least implements Clone). Both of these are explicit actions - whereas a Copy type would not require any explicit action for it to be duplicated in a context which would otherwise require a move.

Clone is a super-trait of Copy - IOW, to implement Copy, a type must also implement Clone. Not all types implement either of these traits, but of the two, Clone is the first and least restrictive to implement.


  1. https://doc.rust-lang.org/std/clone/trait.Clone.html ↩︎

  2. https://doc.rust-lang.org/std/marker/trait.Copy.html ↩︎