Moves, copies and clones in Rust

原文链接:Moves, copies and clones in Rust

Introduction

Moves and copies are fundamental concepts in Rust. These might be completely new to programmers coming from garbage collected languages like Ruby, Python or C#. While these terms do exist in C++, their meaning in Rust is subtly different. In this post I'll explain what it means for values to be moved, copied or cloned in Rust. Let's dive in.

move 和 copy 是 Rust 中的基础概念。这对于来自 Ruby、Python 或 C#等垃圾回收语言的程序员来说可能是完全陌生的。这些术语在 C++中也确实存在,但它们在 Rust 中的含义却有微妙的不同。在本文中,我将解释对值进行 move、copy 和 clone 在 Rust 中到底意味着什么?让我们开始吧。

Moves

As shown in Memory safety in Rust - part 2, assigning one variable to another transfers the ownership to the assignee:

正如在Memory safety in Rust - part 2 所展示的,把一个变量赋值给另一个变量会把所有权(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 it mean to move v? To understand that, we need to see how a Vec is laid out in memory:

在上面的例子中,v被move到v1。但是move v意味着什么?要想理解这个问题,我们需要先来看一下一个Vec在内存中是如何布局的:

 A Vec has to maintain a dynamically growing or shrinking buffer. This buffer is allocated on the heap and contains the actual elements of the Vec. In addition, a Vec also has a small object on the stack. This object contains some housekeeping information: a pointer to the buffer on the heap, the capacity of the buffer and the length (i.e. how much of the capacity is currently filled).

Vec不得不维护一个动态增长或收缩(shrinking)的缓冲区(buffer)。这个缓冲区被分配在堆上,包含Vec里真正的元素。此外,Vec还在栈上有一个小的对象。这个对象包含某些内务信息:一个指向堆上缓冲区的指针(pointer) ,缓存区的容量(capacity) 和长度(length) (比如,当前被填满的容量是多少)。

When the variable v is moved to v1, the object on the stack is bitwise copied:

当变量v被move到v1时,栈上的对象被逐位拷贝(bitwise copy):

  • What has essentially happened in the previous example is a shallow copy. This is in stark contrast to C++, which makes a deep copy when a vector is assigned to another variable.
  • “ 在前面的例子中,本质上发生的是一个浅拷贝(shallow copy)。这与C++形成了鲜明的对比,当一个向量被赋值给另一个变量时,C++会进行深拷贝(deep copy)。

The buffer on the heap stays intact. This is indeed a move: it is now v1's responsibility to drop the heap buffer and v can't touch it:

堆上的缓冲区保持不变。这确实是一个move:现在v1负责释放缓冲区,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 good because if access was allowed through both v and v1 then you will end up with two stack objects pointing to the same heap buffer:

这个所有权的改变很好,因为如果vv1都被允许访问,那么就有两个栈上的对象指向相同的堆缓冲区。

Which object should drop the buffer in this case? Because that is not clear, Rust prevents this situation from arising at all.

这种情况,应该由哪个对象释放缓冲区呢?因为这是不清晰的,所以Rust从根本上避免了这种情况的出现。

Assignment is not the only operation which involves moves. Values are also moved when passed as arguments or returned from functions:

赋值不是唯一涉及到move的操作。当传递参数或者从函数返回的时候,值也会被move:

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 assigned to members of a struct 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);

That's all about moves. Next let's take a look at copies.

以上就是关于move的全部内容。下面让我们来看一下copy。

Copies

Remember this example from 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 type of the variables v and v1 from Vec to i32:

如果我们把变量vv1的类型从Vec改为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 operator move v into v1 this time? To see that, let's take a look at the memory layout again:

这几乎是相同的代码。为什么这次赋值没有把v move到v1呢?要想理解这个,我们需要再来看一下内存布局:

 

In this example the values are contained entirely in the stack. There is nothing to own on the heap. That is why it is ok to allow access through both v and v1 — they are completely independent copies.

在这个例子中,值完全被包含在栈上。在堆上什么都没有拥有。这就是为什么vv1都被允许访问是没有问题的——它们是完全独立的拷贝(copy)。

Such types which do not own other resources and can be bitwise copied are called Copy types. They implement the Copy marker trait. All primitive types like integers, floats and characters are Copy. Structs or enums are not Copy by default but you can derive the Copy trait:

像这样没有拥有其他资源的类型且可以被逐位拷贝(bitwise copy)的类型被称为Copy类型。它们实现了Copy marker trait 。所有的基本类型,像整数,浮点数和字符都是Copy类型。结构体或枚举默认不是Copy但是你可以派生(derive)自一个Copy trait:

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

#[derive(Copy, Clone)]
enum SignedOrUnsignedInt {
    Signed(i32),
    Unsigned(u32),
}
  • Clone in the derive clause is needed because Copy is defined like this: pub trait Copy: Clone {}
  • 在派生语句中的Clone是需要的,因为Copy的定义类似这样:pub trait Copy:Clone {}

For #[derive(Copy, Clone)] to work, all the members of the struct or enum must be Copy themselves. For example, this will not work:

为了能让#[derive(Copy, Clone)]正常工作,结构体或枚举的所有成员自身必须是Copy类型。例如,下面这样就无法正常工作:

//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>
}

You can of course also implement Copy and Clone manually:

当然,你也可以手动实现CopyClone

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

//no method in Copy because it is a marker trait
impl Copy for Point {}

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

In general, any type that implements Drop cannot be Copy because Drop is implemented by types which own some resource and hence cannot be simply bitwise copied. But Copy types should be trivially copyable. Hence, Drop and Copy don't mix well.

通常来讲,任何实现了Drop的类型都不能被Copy,因为Drop是被拥有其他资源的类型来实现的,且因此不能被简单地逐位拷贝。但是Copy类型应该是可以被拷贝的。因此,DropCopy不能很好地混合在一起使用。

And that's all about copies. On to clones.

以上就是关于copy的内容,下面是clone。

Clones

When a value is moved, Rust does a shallow copy; but what if you want to create a deep copy like in C++? To allow that, a type must first implement the Clone trait. Then to make a deep copy, client code should call the clone method:

当一个值被move的时候,Rust做一个浅拷贝;但是如果你想像在C++里那样创建一个深拷贝该怎么办呢?要实现这个,这个类型必须首先实现Clone trait 。接着做一个深拷贝,客户端代码应该调用clone方法:

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

This results in the following memory layout after the clone call:

clone调用后,就产生了下面的内存布局:

Due to deep copying, both v and v1 are free to independently drop their heap buffers.

由于是深拷贝,vv1可以自由独立地释放它们的堆缓冲区。

  • The clone method doesn't always create a deep copy. Types are free to implement clone any way they want, but semantically it should be close enough to the meaning of duplicating an object. For example, Rc and Arc increment a reference count instead.
  • clone方法不总是会创建一个深拷贝。类型可以以任意想要的方式自由实现clone,但是语义上,它应该足够接近复制一个对象的含义。例如,RcArc取而代之的是增加了一个引用计数。

And that's all about clones.

这就是关于clone的全部内容。

Conclusion

In this post I took a deeper look at semantics of moves, copies and clones in Rust. I have tried to capture the nuance in meaning when compared with C++.

在本文中,我更深入地研究了Rust中move、copy和clone的语义。我试图捕捉到与C++相比在意义上的细微差别。

Rust is great because it has great defaults. For example, the assignment operator in Rust either moves values or does trivial bitwise copies. In C++, on the other hand, an innocuous looking assignment can hide loads of code that runs as part of overloaded assignment operators. In Rust, such code is brought into the open because the programmer has to explicitly call the clone method.

Rust很优秀,因为它有优秀的默认值。例如,Rust中的赋值操作符要么移动值,要么做简单的逐位拷贝。另一方面,在C++中,一个看似无害的赋值可能隐藏了大量的代码,这些代码作为重载赋值操作符的一部分运行。在Rust中,这样的代码被公开,因为程序员必须显式地调用clone方法。

One could argue that both languages make different trade-offs but I like the extra safety guarantees Rust brings to the table due to these design choices.

可以说,这两种语言做出了不同的取舍,但我喜欢Rust由于这些设计选择而带来的额外的安全保障。

 

posted @ 2023-04-14 22:12  ImreW  阅读(9)  评论(0编辑  收藏  举报