Menu
Coddy logo textTech

Clone и Copy

Часть раздела Объектно-ориентированное программирование путешествия по Rust на Coddy. Урок 37 из 61.

В Rust, когда ты присваиваешь значение another переменной, владение обычно перемещается: исходная переменная становится недействительной. Однако два стандартных трейта изменяют такое поведение: Clone и Copy.

Трейт Copy обеспечивает неявное побитовое копирование. Когда тип реализует 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 обеспечивает явное глубокое копирование с помощью метода .clone(). Он требуется всякий раз, когда вы derive Copy, но также может использоваться отдельно для типов, которым необходимо явное копирование:

let p3 = p1.clone();  // Явная копия

Есть важное ограничение: Copy можно вывести только для типов, у которых все поля также реализуют Copy. Простые типы, такие как целые числа и числа с плавающей точкой, являются Copy, а String — нет. Он управляет памятью в куче. Если ваша структура содержит String, вы можете вывести только Clone, но не Copy.

ПризнакПоведениеИспользование
CopyНеявное, автоматическоеПростые данные, хранящиеся только в стеке
CloneЯвное через .clone()Любые данные, которые можно дублировать

Для простых структур с примитивными полями вывод обоих трейтов позволяет свободно присваивать значения, не беспокоясь о владении.

challenge icon

Задание

Легко

Давайте создадим систему координат, которая демонстрирует разницу между трейтами Copy и Clone! Вы создадите две структуры: одну, которую можно неявно скопировать, и другую, для которой требуется явное клонирование, чтобы увидеть, как Rust по-разному обрабатывает дублирование в зависимости от производных трейтов.

Вы организуете код в двух файлах:

  • coordinates.rs: определите две публичные структуры, представляющие разные виды координат:
    • GridPoint: с публичными полями x и y (оба типа i32). Поскольку она содержит только примитивные типы, выведите оба трейта — Copy и Clone, чтобы её можно было неявно скопировать при присваивании.
    • NamedLocation: с публичным полем name (String) и полями x/y (типа i32). Поскольку она содержит String, можно вывести только Clone, но не Copy. Для дублирования этой структуры потребуются явные вызовы .clone().
  • main.rs: подключите модуль coordinates и продемонстрируйте, как каждая структура по-разному ведёт себя при дублировании. Создайте экземпляры, используя предоставленные inputs, а затем покажите, что:
    • Экземпляр GridPoint можно присвоить другой переменной, и обе переменные останутся действительными (неявное копирование)
    • Для создания duplicate экземпляра NamedLocation необходимо использовать .clone(), сохранив исходный экземпляр действительным

В выводе должны отображаться исходные и скопированные/клонированные значения, чтобы подтвердить действительность обеих переменных после дублирования:

Original point: ({x}, {y})
Copied point: ({x}, {y})
Original location: {name} at ({x}, {y})
Cloned location: {name} at ({x}, {y})

Например, при inputs 5, 10 и Home:

Original point: (5, 10)
Copied point: (5, 10)
Original location: Home at (5, 10)
Cloned location: Home at (5, 10)

Вы получите три inputs: координату 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: Создать экземпляр GridPoint с x и y

    // TODO: Продемонстрировать неявное копирование путём присваивания другой переменной
    // (GridPoint реализует Copy, поэтому это создаёт копию автоматически)

    // TODO: Вывести исходную и скопированную точку
    // Format: "Original point: ({x}, {y})"
    // Формат: "Copied point: ({x}, {y})"

    // TODO: Создать экземпляр NamedLocation с name, x и y

    // TODO: Продемонстрировать явное клонирование (NamedLocation реализует только Clone, а не Copy)
    // Используйте .clone() для создания дубликата

    // TODO: Print original and cloned location
    // Format: "Original location: {name} at ({x}, {y})"
    // Format: "Cloned location: {name} at ({x}, {y})"
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор Rust