Введение: почему Copy и Clone — не одно и то же?
Каждый, кто начинает изучать Rust, рано или поздно сталкивается с двумя загадочными трейтами: Copy и Clone. На первый взгляд они делают одно и то же — позволяют получить дубликат значения. Но дьявол, как всегда, кроется в деталях.
Представьте, что вы пишете программу и передаёте значение в функцию. В C++ это было бы копирование (или перемещение, если повезло). В Python — просто ещё одна ссылка на объект. А в Rust... Rust заставляет вас явно решать: "Я хочу, чтобы это значение было скопировано" или "Я хочу клонировать его с осознанием затрат".
Именно здесь и проходит граница между Copy и Clone. Первый — это "лёгкое копирование бит за битом". Второй — "осознанное, возможно дорогое, дублирование".
Давайте разберёмся на примерах, когда и что использовать, чтобы ваш код на Rust был не только правильным, но и эффективным.
Коротко о главном: Copy vs Clone
Что такое Clone?
Clone — это трейт, который даёт вам метод .clone(). Вызывая его, вы явно говорите компилятору: "Я знаю, что это может быть затратно, но мне нужна независимая копия".
Особенности Clone:
- Может быть реализован для любого типа
- Требует явного вызова
.clone() - Может выполнять глубокое копирование (например, для
VecилиString) - Не меняет семантику владения — после клонирования у вас два независимых значения
Что такое Copy?
Copy — это "маркерный" трейт (без методов). Если тип реализует Copy, компилятор может просто копировать его биты при присваивании или передаче в функцию. Это не требует вызова .clone().
Особенности Copy:
- Работает только для простых типов, которые можно безопасно скопировать побитово
- Не требует явного вызова — копирование происходит автоматически
- Тип всё ещё должен реализовывать
Clone(это супертрейт) - Исходное значение остаётся валидным после копирования
Практические примеры: как это работает в коде
Пример 1: Базовые типы (Copy)
Целые числа, числа с плавающей точкой, булевы значения — все они реализуют Copy:
fn main() {
let x = 42;
let y = x; // x копируется в y, x всё ещё доступен
println!("x = {}, y = {}", x, y); // Работает!
}
Здесь x — это i32, который реализует Copy. Поэтому при присваивании y = x происходит копирование, и x остаётся в силе.
Пример 2: String (только Clone)
А вот String — сложный тип, хранящий данные в куче. Он реализует Clone, но не Copy:
fn main() {
let s1 = String::from("Привет, Rust!");
let s2 = s1; // s1 перемещается в s2
// println!("{}", s1); // Ошибка! s1 больше не владеет данными
let s3 = s2.clone(); // А вот так можно — явное клонирование
println!("s2 = {}, s3 = {}", s2, s3); // Работает!
}
Почему String не может быть Copy? Потому что побитовое копирование String привело бы к двум указателям на одну и ту же память в куче. При drop обоих значений — двойное освобождение памяти. Clone же создаёт полноценную независимую копию.
Пример 3: Собственный тип с Copy
Вы можете сделать свой тип Copy, если он состоит только из Copy-типов:
#[derive(Debug, Copy, Clone)]
struct Point {
x: f64,
y: f64,
}
fn main() {
let p1 = Point { x: 1.0, y: 2.0 };
let p2 = p1; // Копируется, p1 остаётся
println!("p1 = {:?}, p2 = {:?}", p1, p2);
}
Пример 4: Собственный тип без Copy
Если ваш тип содержит String или Vec, Copy не сработает:
#[derive(Debug, Clone)] // Только Clone!
struct User {
name: String,
age: u32,
}
fn main() {
let user1 = User {
name: String::from("Алиса"),
age: 30,
};
// let user2 = user1; // Ошибка! user1 перемещается
let user2 = user1.clone(); // Так можно
println!("{:?} и {:?}", user1, user2);
}
Когда использовать Copy, а когда Clone?
Copy — для "лёгких" типов
- Числовые типы:
i32,u64,f32,f64 - Булевы значения:
bool - Символы:
char - Кортежи и массивы, состоящие из
Copy-типов - Собственные структуры, где все поля —
Copy
Золотое правило: Если ваш тип можно представить как простой набор битов фиксированного размера, хранящийся на стеке — смело добавляйте Copy.
Clone — для всего остального
String,Vec<T>,HashMap- Структуры с любыми "кучными" полями
- Любые типы, где копирование требует выделения памяти
Важно: Clone — это явное действие. Вы не можете случайно клонировать что-то дорогое. Компилятор заставит вас написать .clone().
Под капотом: как это работает с точки зрения памяти
Стек и куча
Типы Copy обычно хранятся на стеке. Копирование — это просто копирование байтов из одного места стека в другое. Это невероятно быстро.
Типы Clone могут хранить данные в куче. .clone() выделяет новую память, копирует содержимое и возвращает новое значение. Это может быть дорого, особенно для больших структур данных.
Вот почему Copy — это "бесплатное