Copy и Clone в Rust: в чём разница и что выбрать?

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: почему 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 — это "бесплатное

Блоги

Рекомендаций по книгам