Rust中复制Copy和克隆Clone区别


复制Copy和克隆Clone是“类型”属性的一种特性traits,与“值”属性无关。
复制与克隆并不等同于栈与堆,它们与所有权的关系比与内存区域的关系更密切。

想想一下这个例子,整数存储在堆上(通过Vec),需要从一个元素复制到另一个。
这里使用了Copy(因为没有明确的.clone()),但是源和目的都在堆上。

let mut v = vec! [0i32; 10];
v[0] = v[1];

再看看这个例子,RefCell是存储在栈上的,并使用Clone来获得另一个相同的元素。两个元素都在栈上。
这里必须使用.clone(),因为RefCell没有实现Copy,RefCell也不使用任何堆分配。

let rc1 = RefCell::new(...);
let rc2 = rc1.clone();

一个实现了Copy的类型仅仅意味着它可以从一个内存位置一点一点地复制到另一个内存位置,并且两个实例同样有效。

何时选择Copy和Clone?当类型被创建或销毁时,除了它所持有的就地数据外,是否其他额外的意义需要被应用?
拥有堆分配内存的所有权是其中一种情况,因为当所有者被销毁时,你需要取消这些分配的内存。像RefCell那样保留额外的记账记录,也会使它失去实现复制的资格,因为对数据进行简单的位拷贝会产生不一致或无法使用的结果。
管理像文件句柄这样的外部资源是另一种情况,因为你必须与操作系统协调,明确open()和close()文件。

与堆、栈关系
Clone是为任意复制而设计的,而Copy则代表可以通过memcpy安全复制的值。

memcpy是复制字节缓冲区的低级系统函数。在伪代码memcpy中,执行以下操作:

memcpy(to, from, size):
  for i from 0 to size:
    to[i] = from[i]

也就是说,它实际上是从一个内存位置到另一个固定长度的字节的简单的、逐个元素的副本。但它的实施非常有效。
如果一个类型是memcpy-able,这意味着它的内存表示不包含依赖于它的内存位置的逻辑。也就是说,一旦它被复制或移动到不同的内存地址,它仍然有效。

这对i32或f64等类型来说是有效的。但是对于向量Vec来说就不是这样了,因为向量Vec通常被实现为一个有大小的容量和一个指向另一个缓冲区的指针。
如果我们通过memcpy复制一个向量,新对象中的这个指针现在仍然会指向旧对象的缓冲区。
这就是为什么i32等实现了Copy,而std::vec::Vec却没有。