Rust中的Copy与Clone:有什么区别以及如何选择?

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

引言:为什么Copy和Clone不是一回事?

\n\n

每个开始学习Rust的人,迟早都会遇到两个神秘的trait:Copy和Clone。乍一看,它们做的是同一件事——允许你获取一个值的副本。但魔鬼,总是藏在细节中。

\n\n

想象一下,你正在编写一个程序,并将一个值传递给函数。在C++中,这会是复制(或者移动,如果运气好的话)。在Python中,只是对对象的另一个引用。而在Rust中……Rust迫使你明确地决定:“我希望这个值被复制”或者“我希望在意识到成本的情况下克隆它”。

\n\n

这正是Copy和Clone之间的分界线。前者是“逐位轻量级复制”。后者是“有意识的、可能代价高昂的复制”。

\n\n

让我们通过示例来理解何时使用哪一个,以便你的Rust代码不仅正确,而且高效。

\n\n

核心要点:Copy vs Clone

\n\n

什么是Clone?

\n\n

Clone是一个trait,它为你提供了.clone()方法。调用它时,你明确地告诉编译器:“我知道这可能代价高昂,但我需要一个独立的副本。”

\n\n

Clone的特点:

\n
    \n
  • 可以为任何类型实现
  • \n
  • 需要显式调用.clone()
  • \n
  • 可以执行深拷贝(例如,对于Vec或String)
  • \n
  • 不改变所有权语义——克隆后,你拥有两个独立的值
  • \n
\n\n

什么是Copy?

\n\n

Copy是一个“标记”trait(没有方法)。如果一个类型实现了Copy,编译器可以在赋值或传递给函数时简单地复制其位。这不需要调用.clone()。

\n\n

Copy的特点:

\n
    \n
  • 仅适用于可以安全逐位复制的简单类型
  • \n
  • 不需要显式调用——复制是自动发生的
  • \n
  • 该类型仍然必须实现Clone(它是超trait)
  • \n
  • 原始值在复制后仍然有效
  • \n
\n\n

实践示例:代码中的工作原理

\n\n

示例1:基本类型(Copy)

\n\n

整数、浮点数、布尔值——它们都实现了Copy:

\n\n
fn main() {\n    let x = 42;\n    let y = x; // x被复制到y,x仍然可用\n    \n    println!("x = {}, y = {}", x, y); // 有效!\n}
\n\n

这里x是i32类型,它实现了Copy。因此,在赋值y = x时发生复制,x仍然有效。

\n\n

示例2:String(仅Clone)

\n\n

而String是一个复杂类型,在堆上存储数据。它实现了Clone,但没有实现Copy:

\n\n
fn main() {\n    let s1 = String::from("你好,Rust!");\n    let s2 = s1; // s1被移动到s2\n    \n    // println!("{}", s1); // 错误!s1不再拥有数据\n    \n    let s3 = s2.clone(); // 这样可以——显式克隆\n    println!("s2 = {}, s3 = {}", s2, s3); // 有效!\n}
\n\n

为什么String不能是Copy?因为对String进行逐位复制会导致两个指针指向堆中的同一块内存。当两个值都被drop时,会导致双重释放内存。而Clone则创建一个完整的独立副本。

\n\n

示例3:自定义类型与Copy

\n\n

如果你的类型仅由Copy类型组成,你可以使其成为Copy:

\n\n
#[derive(Debug, Copy, Clone)]\nstruct Point {\n    x: f64,\n    y: f64,\n}\n\nfn main() {\n    let p1 = Point { x: 1.0, y: 2.0 };\n    let p2 = p1; // 被复制,p1仍然有效\n    \n    println!                

博客

书籍推荐