100-exercises-to-learn-rust/book/src/04_traits/10_clone.md

3.4 KiB

Copying values, pt. 1

In the previous chapter we introduced ownership and borrowing.
We stated, in particular, that:

  • Every value in Rust has a single owner at any given time.
  • When a function takes ownership of a value ("it consumes it"), the caller can't use that value anymore.

These restrictions can be somewhat limiting.
Sometimes we might have to call a function that takes ownership of a value, but we still need to use that value afterward.

fn consumer(s: String) { /* */ }

fn example() {
     let mut s = String::from("hello");
     consumer(s);
     s.push_str(", world!"); // error: value borrowed here after move
}

That's where Clone comes in.

Clone

Clone is a trait defined in Rust's standard library:

pub trait Clone {
    fn clone(&self) -> Self;
}

Its method, clone, takes a reference to self and returns a new owned instance of the same type.

In action

Going back to the example above, we can use clone to create a new String instance before calling consumer:

fn consumer(s: String) { /* */ }

fn example() {
     let mut s = String::from("hello");
     let t = s.clone();
     consumer(t);
     s.push_str(", world!"); // no error
}

Instead of giving ownership of s to consumer, we create a new String (by cloning s) and give that to consumer instead.
s remains valid and usable after the call to consumer.

In memory

Let's look at what happened in memory in the example above. When let mut s: String::from("hello"); is executed, the memory looks like this:

                    s
      +---------+--------+----------+
Stack | pointer | length | capacity | 
      |  |      |   5    |    5     |
      +--|------+--------+----------+
         |
         |
         v
       +---+---+---+---+---+
Heap:  | H | e | l | l | o |
       +---+---+---+---+---+

When let t = s.clone() is executed, a whole new region is allocated on the heap to store a copy of the data:

                    s                                    s
      +---------+--------+----------+      +---------+--------+----------+
Stack | pointer | length | capacity |      | pointer | length | capacity |
      |  |      |   5    |    5     |      |  |      |   5    |    5     |
      +--|------+--------+----------+      +--|------+--------+----------+
         |                                    |
         |                                    |
         v                                    v
       +---+---+---+---+---+                +---+---+---+---+---+
Heap:  | H | e | l | l | o |                | H | e | l | l | o |
       +---+---+---+---+---+                +---+---+---+---+---+

If you're coming from a language like Java, you can think of clone as a way to create a deep copy of an object.

Implementing Clone

To make a type Clone-able, we have to implement the Clone trait for it.
You almost always implement Clone by deriving it:

#[derive(Clone)]
struct MyType {
    // fields
}

The compiler implements Clone for MyType as you would expect: it clones each field of MyType individually and then constructs a new MyType instance using the cloned fields.
Remember that you can use cargo expand (or your IDE) to explore the code generated by derive macros.

References

  • The exercise for this section is located in exercises/04_traits/10_clone