Menu
CoddyTech

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 后,你就可以自由地赋值,而不必担心所有权。

challenge icon

挑战

简单

让我们构建一个坐标系,展示 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})"
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: Rust 在线编译器