Clone 和 Copy
本课属于 Coddy Rust 学习旅程的“面向对象编程”单元。第 37 课,共 61 课。
在 Rust 中,将一个值赋给另一个变量时,所有权通常会移动:原变量会失效。不过,两个标准 trait 会改变这种行为:Clone 和 Copy。
Copy trait 支持隐式按位复制。当某个类型实现了 Copy 时,将它赋值给另一个变量会自动创建副本,而不是转移所有权:
#[derive(Copy, Clone)]
struct Point {
x: i32,
y: i32,
}
let p1 = Point { x: 10, y: 20 };
let p2 = p1; // p1 被复制了,没有被移动
println!("{}, {}", p1.x, p2.x); // 两者都有效!
Clone trait 通过 .clone() 方法提供显式深拷贝。派生 Copy 时必须实现它,但对于需要显式复制的类型,也可以单独使用:
let p3 = p1.clone(); // 显式复制
有一个重要的限制:只有当所有字段也都实现了 Copy 时,类型才能派生 Copy。整数和浮点数等简单类型实现了 Copy,但 String 没有。它管理堆内存。如果你的结构体包含 String,就只能派生 Clone,不能派生 Copy。
| 特征 | 行为 | 用途 |
|---|---|---|
Copy | 隐式,自动 | 简单的仅栈数据 |
Clone | 通过 .clone() 显式执行 | 任何可复制的数据 |
对于字段为基本类型的简单结构体,派生这两种 trait 后,你就可以自由地赋值,而不必担心所有权。
挑战
简单让我们构建一个坐标系,展示 Copy 和 Clone trait 的区别!你将创建两个结构体,一个可以隐式复制,另一个需要显式克隆,从而了解 Rust 如何根据派生的 trait 以不同方式处理复制。
你将在两个文件中组织代码:
coordinates.rs:定义两个表示不同类型坐标的公共结构体:GridPoint:包含公共字段x和y(两者都是i32)。由于它只包含基本类型,因此派生Copy和Clone,这样赋值时就可以隐式复制。NamedLocation:包含公共字段name(String)以及x/y字段(i32)。由于它包含String,你只能派生Clone,不能派生Copy。要复制这个结构体,需要显式调用.clone()。
main.rs:引入 coordinates 模块,并演示复制时这两个结构体的不同行为。使用提供的输入创建实例,然后展示:- 可以将
GridPoint赋值给另一个变量,并且两者仍然有效(隐式复制) - 必须使用
.clone()创建NamedLocation的副本,同时保留原对象的有效性
- 可以将
你的输出应显示原始值以及复制或克隆后的值,以证明复制后两个变量都有效:
Original point: ({x}, {y})
Copied point: ({x}, {y})
Original location: {name} at ({x}, {y})
Cloned location: {name} at ({x}, {y})例如,输入 5、10 和 Home 时:
Original point: (5, 10)
Copied point: (5, 10)
Original location: Home at (5, 10)
Cloned location: Home at (5, 10)你将收到三个输入:x 坐标(解析为 i32)、y 坐标(解析为 i32)和位置名称。
亲手试试
mod coordinates;
use coordinates::{GridPoint, NamedLocation};
fn main() {
// 读取输入
let mut input1 = String::new();
std::io::stdin().read_line(&mut input1).expect("Failed to read line");
let x: i32 = input1.trim().parse().expect("Invalid number");
let mut input2 = String::new();
std::io::stdin().read_line(&mut input2).expect("Failed to read line");
let y: i32 = input2.trim().parse().expect("Invalid number");
let mut input3 = String::new();
std::io::stdin().read_line(&mut input3).expect("Failed to read line");
let name = input3.trim().to_string();
// TODO:用 x 和 y 创建一个 GridPoint 实例
// TODO:通过赋值给另一个变量演示隐式复制
// (GridPoint 实现了 Copy,因此会自动创建副本)
// TODO:打印原始点和复制的点
// 格式:"Original point: ({x}, {y})"
// 格式:"Copied point: ({x}, {y})"
// TODO:用 name、x 和 y 创建一个 NamedLocation 实例
// TODO:演示显式克隆(NamedLocation 只实现了 Clone,没有实现 Copy)
// 使用 .clone() 创建副本
// TODO:打印原始位置和克隆的位置
// 格式:"Original location: {name} at ({x}, {y})"
// 格式:"Cloned location: {name} at ({x}, {y})"
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: Rust 在线编译器