Чем отличается Copy от Clone в Rust?

В языке Rust Copy и Clone — это два разных способа дублирования значений, но с разной семантикой, назначением и влиянием на производительность. Они оба относятся к механизму поверхностного копирования (shallow copy), однако отличаются уровнем контроля и моментом выполнения. Чтобы понять разницу между Copy и Clone, нужно сначала разобраться, как работает владение и перемещение (move) в Rust, поскольку оба этих трейта с ним тесно связаны.

Что такое Copy?

Copy — это трейд (интерфейс), реализация которого означает, что при присваивании или передаче значения в функцию создается его побитовая копия, а не перемещение владения. Такие типы остаются доступными после присваивания, потому что владение не передаётся — создаётся копия автоматически.

Ключевые особенности Copy:

  • Копирование происходит автоматически, без вызова метода.

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

  • Доступен только для типов, не владеющих ресурсами (например, без heap-памяти).

  • Тип должен также реализовывать Clone, но Copy — более "жёсткое" подмножество.

Примеры типов с Copy:

  • Примитивы: i32, u8, bool, char, f64 и т. д.

  • Кортежи из типов, реализующих Copy, например: (i32, bool).

Пример:

fn main() {
let x = 42;
let y = x; // x копируется, не перемещается
println!("{}", x); // x все еще доступен
}

Если тип реализует Copy, то переменная остаётся доступной после копирования. Это делает Copy удобным для "лёгких" значений, с которыми операции дешёвы.

Что такое Clone?

Clone — это тоже трейд, но его реализация позволяет вручную создавать дубликаты значений с помощью метода .clone(). В отличие от Copy, Clone может реализовывать глубокое копирование или любое произвольное поведение.

Ключевые особенности Clone:

  • Копирование требует явного вызова метода .clone().

  • Может быть дорогим по производительности, особенно для heap-структур (String, Vec, Box).

  • Предназначен для владельческих структур — можно создавать новые экземпляры, не перемещая оригинал.

  • Реализуется вручную либо автоматически с помощью #[derive(Clone)].

Пример:

fn main() {
let s1 = String::from("hello");
let s2 = s1.clone(); // вручную копируем строку
println!("{}", s1); // s1 все еще доступна
}

Здесь мы вызываем .clone() у строки String, чтобы создать полную копию в куче. Без .clone() присваивание let s2 = s1 вызвало бы перемещение, и s1 больше нельзя было бы использовать.

Сравнение: Copy vs Clone

Характеристика Copy Clone
Как вызывается Автоматически (при присваивании, передаче) Вручную, через .clone()
--- --- ---
Тип копирования Поверхностное, побитовое Поверхностное или глубокое, зависит от реализации
--- --- ---
Когда используется Для простых типов (числа, bool и т. д.) Для структур с ресурсами (heap, файлы и т. д.)
--- --- ---
Может быть переопределён вручную Нет Да
--- --- ---
Производительность Очень высокая (zero-cost) Может быть дорогой
--- --- ---
Владение ресурсами Не допускается Допускается
--- --- ---

Можно ли реализовать оба трейта?

Да, но с ограничениями. Copy всегда требует Clone, но не наоборот.

Пример:

#\[derive(Copy, Clone)\]
struct Point {
x: i32,
y: i32,
}

Тип Point будет как автоматически клонируемым (Clone), так и автоматически копируемым (Copy), потому что оба его поля реализуют Copy. Но если хотя бы одно поле не реализует Copy, например, String, то и Point не сможет реализовать Copy.

Пример несовместимости:

struct Person {
name: String,
}
impl Clone for Person {
fn clone(&self) -> Self {
Person {
name: self.name.clone(),
}
}
}
// Copy нельзя реализовать, потому что String не Copy

Как Copy и Clone влияют на семантику владения?

В Rust по умолчанию все переменные перемещаются (move) при присваивании или передаче в функции. Однако если тип реализует Copy, то вместо перемещения будет сделана копия. Это даёт разработчику контроль над семантикой передачи данных.

Типы, которые реализуют Clone, позволяют вручную копировать значения, чтобы избежать перемещения и сохранить доступ к оригиналу. Это особенно важно при работе с данными в куче (heap), когда владение имеет значение.

Где использовать Copy, а где Clone?

  • Используйте Copy, когда:

    • тип лёгкий, примитивный;

    • нет необходимости в heap-данных;

    • важна производительность;

    • нужна простота API (например, let y = x; без .clone()).

  • Используйте Clone, когда:

    • работаете с ресурсами (строки, векторы, файлы);

    • необходимо точное управление копированием;

    • логика копирования должна быть нестандартной или глубокой;

    • перемещение недопустимо, но нужно сохранить доступ к оригиналу.

Таким образом, Copy — это автоматическое, дешёвое, безопасное копирование для простых типов. Clone — ручное, зачастую дорогое и гибкое копирование, подходящее для сложных структур с владением и внутренними ресурсами. Rust заставляет нас задумываться о выборе между ними, чтобы писать безопасный, предсказуемый и эффективный код.