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() | Любые данные, которые можно дублировать |
Для простых структур с примитивными полями вывод обоих трейтов позволяет свободно присваивать значения, не беспокоясь о владении.
Задание
ЛегкоДавайте создадим систему координат, которая демонстрирует разницу между трейтами 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})"
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Методы и поведение
Введение в блоки реализацииПараметр SelfИзменяемые методыАссоциированные функцииНесколько блоков реализацииЦепочки вызовов методовИтоги: действия с Rectangle4Проект: Виртуальный питомец
Определение питомцаКормление питомца2Инкапсуляция и модули
Основы модулейКлючевое слово PublicПриватные поляГеттерыСеттерыИтоги — Безопасный сейф5Обобщения
Обобщённые структурыОбобщённые методыНесколько обобщённых типовОбобщённые функцииПовторение — Координатная точка8Трейты как ограничения
Синтаксис ограничений трейтовМножественные ограниченияКонструкция whereВозврат типов с трейтамиИтоги — Обобщённый принтер11Паттерны проектирования в Rust
Паттерн NewtypeКомпозицияТрейт DropFrom и IntoИтоги — Мок-объект Smart Pointer3Продвинутые перечисления
Перечисления с даннымиМетоды в перечисленияхСопоставление вариантов с даннымиСнова об Option EnumИтоги — перечисление Shape6Определение трейтов
Что такое трейт?Реализация трейтовРеализации по умолчаниюПереопределение реализаций по умолчаниюТрейты с параметрамиИтоги — Media PlayerПотренируйтесь самостоятельно: Онлайн-компилятор Rust