Take you to know about move, copy and clone in Rust

Posted by playa4real on Wed, 29 Dec 2021 04:48:57 +0100

move, copy, clone

Original text: https://hashrust.com/blog/moves-copies-and-clones-in-rust/

This article does not translate move, copy and clone into Chinese. It keeps the taste in Rust. After translation, it has no taste.

introduce

Move and copy are basic concepts in Rust. These concepts may be completely unfamiliar to developers from languages with garbage collection capabilities such as Ruby, Python, or c#. Although these terms exist in C + +, they have slightly different meanings in Rust. In this article, I will explain the meaning of move, copy and clone in Rust. Let's find out.

Move

Just like " Memory security in Rust - 2 "Said that assigning a variable to another variable will transfer ownership.

let v: Vec<i32> = Vec::new();
let v1 = v;    // v1 is the new owner

In the above example, v is moved to v1. But what does mobile v mean? To understand this, we need to look at the layout structure of Vec in memory:

Vec internally maintains a dynamic growth or contraction buffer. This buffer is allocated on the heap and contains the actual elements of Vec. In addition, Vec has a small object on the stack. This object contains some management information: a pointer to the buffer on the heap, the capacity and length of the buffer (that is, how many parts are currently filled).

When the variable v is moved to v1, the objects on the stack are copied bit by bit:

📒 :
In the above example, what actually happens is a shallow copy (that is, bitwise copy). This is very different from c + +, which makes a deep copy when assigning a vector to another variable.

The buffer on the heap remains unchanged. But here's a move: v1 is now responsible for releasing the buffer on the heap, not v:

let v: Vec<i32> = Vec::new();
let v1 = v;
println!("v's length is {}", v.len());   // error: borrow of moved value: `v`

This change of ownership is beneficial because if you allow access to buffer data through both v and v1, you will get two stack objects pointing to the same Heap Buffer:

In this case, which object has the right to release the buffer? This is not clear, and Rust has fundamentally prevented this from happening. (i.e. assignment → stack object copy, and transfer ownership at the same time to ensure that an object can only have one owner at the same time)

Of course, assignment is not the only operation involving movement. Values are also moved when passed as arguments or returned from functions:

let v: Vec<i32> = Vec::new();
// v is first moved into print_len's v1
// and then moved into v2 when print_len returns it
let v2 = print_len(v);
fn print_len(v1:Vec<i32>) ->Vec<i32> {
    println!("v1's length is {}", v1.len());
    v1     // v1 is moved out of the function
}

Or a member assigned to a structure or enum:

struct Numbers {
    nums:Vec<i32>
}
let v: Vec<i32> = Vec::new();
// v moved into nums field of the Numbers struct
let n = Numbers { nums: v };

enum NothingOrString {
    Nothing,
    Str(String)
}
let s: String = "I am moving soon".to_string();
// s moved into the enum
let nos = NothingOrString::Str(s);

This is all about move. Next, let's look at copy.

Copy

Remember the example above?

let v: Vec<i32> = Vec::new();
let v1 = v;
println!("v's length is {}", v.len());   //error: borrow of moved value: `v`

What happens if we change the types of variables v and v1 from Vec to i32?

let v: i32 = 42;
let v1 = v;
println!("v is {}", v);   // compiles fine, no error!

This is almost the same code. Why doesn't the assignment move v to v1 this time? To understand this, let's look at the memory layout in the stack again:

In this case, the value is completely stored only on the stack. There is nothing on the pile to have. This is why it is possible to allow access through v and v1 -- because they are completely independent copies.

This type that has no other resources and can be copied bit by bit is called a copy type. They did Copy Trait . Currently, all basic types, such as integers, floating-point numbers, and characters, are Copy types. By default, struct/enum is not Copy, but you can derive Copy trait:

#[derive(Copy, Clone)]
struct Point {
    x: i32,
    y: i32,
}

#[derive(Copy, Clone)]
enum SignedOrUnsignedInt {
    Signed(i32),
    Unsigned(u32),
}

📒 :
You need to use clone in #[derive()], because Copy is defined as: pub trait Copy: Clone {}

However, for #[derive(Copy, Clone)] to work, all members of struct or enum must be able to Copy. For example, the following code does not work:

// error:the trait `Copy` may not be implemented for this type
// because its nums field does not implement `Copy`
#[derive(Copy, Clone)]
struct Numbers {
    nums: Vec<i32>
}

Of course, you can also manually implement Copy and Clone:

struct Point {
    x: i32,
    y: i32,
}

// marker trait
implCopy for Point {}

implClone for Point {
fn clone(&self) -> Point {
        *self
    }
}

📒 :
marker trait → itself has no behavior, but is used to provide some guarantees to the compiler. You can see the details here

However, generally speaking, any type that implements Drop cannot be copied, because Drop is implemented by a type that has some resources. Because it cannot be simply copied bit by bit, but the Copy type should be able to be simply copied. Therefore, Drop and Copy do not mix well.

That's all about Copy. Next is Clone.

Clone

When a value is moved, Rust will make a shallow copy; But what if you want to create a deep copy like C + +?

To achieve this, a type must first be implemented Clone Trait . Then, in order to perform deep replication, the caller code should execute clone():

let v: Vec<i32> = Vec::new();
let v1 = v.clone();     // ok since Vec implements Clone
println!("v's length is {}", v.len());//ok

After clone() is called, the memory layout is as follows:

Due to deep copy, both v and v1 can freely and independently release their corresponding heap buffer data.

📒 :
clone() does not always create deep copies. Types are free to implement clone() in any way they want, but semantically it should be close enough to copying the meaning of an object. For example, Rc/Arc increases the reference count.

That's all about Clone.

summary

In this article, I deeply analyzed the semantics of Move/Copy/Clone in Rust. At the same time, the article tries to capture the subtle semantic differences between C + + and.

Rust is good because it has a lot of default behavior. For example, the assignment operator in rust either moves the value (transfers ownership) or makes a simple bitwise copy (shallow copy).

On the other hand, in c + +, seemingly harmless assignment operations can hide a lot of code, which runs as part of overloaded assignment operators. In Rust, such code is public because the programmer must explicitly call clone().

One might say that the two languages make different tradeoffs, but I like the additional security offered by Rust due to these design tradeoffs.

Topics: Rust