Copying and Cloning

While move semantics are the default, certain types are copied by default:

  1. fn main() {
  2. let x = 42;
  3. let y = x;
  4. println!("x: {x}");
  5. println!("y: {y}");
  6. }

These types implement the Copy trait.

You can opt-in your own types to use copy semantics:

  1. #[derive(Copy, Clone, Debug)]
  2. struct Point(i32, i32);
  3. fn main() {
  4. let p1 = Point(3, 4);
  5. let p2 = p1;
  6. println!("p1: {p1:?}");
  7. println!("p2: {p2:?}");
  8. }
  • After the assignment, both p1 and p2 own their own data.
  • We can also use p1.clone() to explicitly copy the data.

Copying and cloning are not the same thing:

  • Copying refers to bitwise copies of memory regions and does not work on arbitrary objects.
  • Copying does not allow for custom logic (unlike copy constructors in C++).
  • Cloning is a more general operation and also allows for custom behavior by implementing the Clone trait.
  • Copying does not work on types that implement the Drop trait.

In the above example, try the following:

  • Add a String field to struct Point. It will not compile because String is not a Copy type.
  • Remove Copy from the derive attribute. The compiler error is now in the println! for p1.
  • Show that it works if you clone p1 instead.

If students ask about derive, it is sufficient to say that this is a way to generate code in Rust at compile time. In this case the default implementations of Copy and Clone traits are generated.