复制和克隆有什么区别?

255

这个问题似乎意味着这只是一种实现细节(memcpy与???),但我找不到任何明确描述差异的内容。


5
Rust 的源代码中有相关的解释,请查看此链接:https://github.com/rust-lang/rust/blob/2e6eaceedeeda764056eb0e2134735793533770d/src/libcore/marker.rs#L272 - duan
2
@duan 这些评论在官方文档网站上:Copy 和 Clone 有什么区别,以及更多关于 Clone 的内容都在文档页面的顶部。 - CircArgs
6个回答

218

Clone旨在进行任意复制:类型TClone实现可以执行创建新T所需的任意复杂操作。它是一个普通的trait(除了出现在prelude中),因此需要像普通trait一样使用,包括方法调用等。

Copy trait表示可以通过memcpy安全复制的值:诸如重新赋值和按值传递参数给函数等操作始终是memcpy,因此对于Copy类型,编译器知道它不需要考虑那些移动


15
我可以理解为Clone是深拷贝,而Copy是浅拷贝吗? - Djvu
38
Clone 的作用是打开这个类型可以进行深拷贝或浅拷贝的 可能性:"任意复杂"。 - poolie
2
有趣的是,我认为Copy只是允许使Clone特征隐式化的一部分。我会把参考资料留在这里,因为我可能不是唯一一个困惑的人。现在手册中已经回答了这个确切的问题(可能在提问时没有):“复制是隐式发生的,例如作为赋值y = x的一部分。复制的行为不能被重载;它总是一个简单的按位复制。” https://doc.rust-lang.org/std/marker/trait.Copy.html#whats-the-difference-between-copy-and-clone - vincent

147
主要区别在于克隆是显式的。隐式表示移动非 Copy 类型。
// u8 implements Copy
let x: u8 = 123;
let y = x;
// x can still be used
println!("x={}, y={}", x, y);

// Vec<u8> implements Clone, but not Copy
let v: Vec<u8> = vec![1, 2, 3];
let w = v.clone();
//let w = v // This would *move* the value, rendering v unusable.

顺便说一下,每个Copy类型也必须是Clone。然而,它们不需要做相同的事情!对于您自己的类型,.clone()可以是您选择的任意方法,而隐式复制将始终触发memcpy,而不是clone(&self)实现。


2
很酷!这解决了我对于克隆特质是否提供隐式复制的一个次要疑问。原来这个问题和这个问题之间的关联性比我想象的要大。谢谢! - user12341234
在您的第一个示例中,假设您希望 y 获取移动的 x,而不是像您最后注释掉的示例 w = v 中的副本。您该如何指定? - johnbakers
3
你不能,也没有必要这样做,因为Copy是为“廉价”类型设计的,比如示例中的u8。如果你编写的是相当重量级的类型,你认为移动比复制更有效率,那就别实现 Copy。需要注意的是,在u8的情况下,你不可能通过移动来更有效地操作,因为在底层它可能至少涉及指针复制——这已经与u8复制一样昂贵了,所以还有什么必要呢。 - mdup
这是否意味着 Copy trait 的存在会影响变量的隐式生命周期范围?如果是,我认为这值得注意。 - Brian Cain

52

如其他回答所述:

  • Copy是隐式的、廉价的,不能重新实现(memcpy)。
  • Clone是显式的,可能很昂贵,并且可以任意重新实现。

讨论CopyClone时有时会忽略的一点是它还影响编译器如何使用移动和自动复制。例如:

#[derive(Debug, Clone, Copy)]
pub struct PointCloneAndCopy {
    pub x: f64,
}

#[derive(Debug, Clone)]
pub struct PointCloneOnly {
    pub x: f64,
}

fn test_copy_and_clone() {
    let p1 = PointCloneAndCopy { x: 0. };
    let p2 = p1; // because type has `Copy`, it gets copied automatically.
    println!("{:?} {:?}", p1, p2);
}

fn test_clone_only() {
    let p1 = PointCloneOnly { x: 0. };
    let p2 = p1; // because type has no `Copy`, this is a move instead.
    println!("{:?} {:?}", p1, p2);
}

(Rust Playground)

第一个例子(PointCloneAndCopy)之所以能在这里正常工作,是因为有隐式复制,但第二个例子(PointCloneOnly)会出现使用已移动值的错误:

error[E0382]: borrow of moved value: `p1`
  --> src/lib.rs:20:27
   |
18 |     let p1 = PointCloneOnly { x: 0. };
   |         -- move occurs because `p1` has type `PointCloneOnly`, which does not implement the `Copy` trait
19 |     let p2 = p1;
   |              -- value moved here
20 |     println!("{:?} {:?}", p1, p2);
   |                           ^^ value borrowed here after move

为了避免隐式移动,我们可以显式调用let p2 = p1.clone();

这可能会引发如何强制移动实现Copy trait的类型的问题?

简短回答:你不能 / 没有意义。


@Shepmaster 我已经将它删除了,尽管我认为它更易读,因为它包含了 Rust 编译器的漂亮颜色编码,并且我特别确保所有与搜索相关的单词也包含在文本中。 - bluenote10
克隆可以更便宜,对吧?当它是浅拷贝时。 - jangorecki
@jangorecki 不太确定。我的理解是Copy只是在内存中对结构进行了memcpy。这是尽可能“浅”的操作,不应与“深复制”混淆。最便宜的可能不会丢失信息的Clone基本上必须模拟一个memcpy。也许如果Clone不复制所有字段,则可以更便宜地实现。 - bluenote10

10

请看这里

复制是隐式发生的,例如作为赋值的一部分 y = xCopy 的行为不能被重载; 它总是一个简单的按位复制。

克隆是显式动作,x.clone()Clone 的实现可以提供任何类型特定的行为以安全地复制值。例如,StringClone 实现需要复制堆中指向的字符串缓冲区。对于 String 值的简单按位复制只会复制指针,导致下游出现双重释放。因此,StringClone 但不是 Copy

CloneCopy的超级特性,因此所有实现了Copy的类型也必须实现Clone。如果一个类型是Copy,那么其Clone实现只需要返回*self


2

在我看来,意图就是你所需要的一切

  • 两者都可以用于避免借用和生命周期注释或智能指针
  • 复制在这里意味着复制语义,或者意味着该值将像Java或JS语言中那样被处理,这意味着该值在某种程度上是原始的或便宜的副本
  • 克隆让您决定复制的深度足够深,并且您必须明确编写.clone,这意味着它已经处理好了

2
我发现这个解释非常有帮助:
在 Rust 中,一些简单的类型是“隐式可复制的”,当你将它们赋值或作为参数传递时,接收者会得到一个副本,原始值保持不变。对于其他类型,必须显式地进行复制,通过实现 Clone trait 并调用 clone() 方法。
Clone trait 定义了显式创建对象 T 的深度复制的能力。当我们为类型 T 调用 Clone 时,它执行所有需要创建新 T 的任意复杂操作。
Rust 中的 Copy trait 定义了隐式复制对象的能力。Copy 的行为不可重载。它总是进行简单的位拷贝。这适用于具有固定大小且完全存储在堆栈上的类型。
参考:https://intmain.co/difference-between-copy-and-clone-trait-in-rust

Clone特质定义了显式创建深拷贝的能力。不,拷贝是任意的,因此不一定是深拷贝。请参考被接受的答案。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接