Avatar ·

В чем разница между трейтами Copy и Clone в Rust?

Я только начал изучать Rust и запутался в двух трейтах — Copy и Clone. Вроде бы оба позволяют копировать значения, но компилятор ведет себя по-разному. Вот пример, который меня смутил. Я пытаюсь скопировать структуру: ```rust struct Point { x: i32, y: i32, } fn main() { let p1 = Point { x: 10, y: 20 }; let p2 = p1; // move let p3 = p1; // error: use of moved value } ``` Если я добавлю `#[derive(Copy)]`, то код работает: ```rust #[derive(Copy, Clone)] struct Point { x: i32, y: i32, } ``` Но если я использую только `Clone`, то приходится явно вызывать `.clone()`: ```rust let p2 = p1.clone(); ``` Почему так? Когда нужно использовать Copy, а когда Clone? В чем принципиальная разница? И почему Copy требует Clone (я видел, что они часто идут вместе в derive)? Объясните, пожалуйста, как новичку. Заранее спасибо! <<>>

Основная разница: семантика перемещения vs копирования

В Rust по умолчанию действует семантика перемещения (move semantics). Когда вы присваиваете значение другой переменной, оригинал становится недоступным. Трейты Copy и Clone — это два способа обойти это поведение, но с разными правилами.

Copy — неявное, дешевое побитовое копирование

Трейт Copy означает: "это значение можно безопасно скопировать, просто скопировав его биты в памяти". Компилятор делает это автоматически при присваивании или передаче в функцию. Вам не нужно вызывать никаких методов.

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

let a = Point { x: 1, y: 2 };
let b = a;        // неявное копирование
let c = a;        // тоже работает, a всё ещё жив
println!("{}", a.x); // OK

Важно:

  • Copy можно реализовать только для типов, которые хранятся на стеке и не владеют динамической памятью (например, i32, f64, bool, char, кортежи из Copy-типов).
  • Если тип содержит String, Vec или другой владеющий тип — реализовать Copy нельзя, это ошибка компиляции.
  • Copy — это супертрейт Clone. Поэтому при #[derive(Copy)] нужно также указывать Clone.

Clone — явное, потенциально дорогое копирование

Трейт Clone требует явного вызова метода .clone(). Он может выполнять сложную логику: выделять память, копировать данные по ссылкам, делать глубокое копирование.

#[derive(Clone)]
struct Person {
    name: String,
    age: u8,
}

let p1 = Person { name: "Alice".to_string(), age: 30 };
let p2 = p1.clone(); // явное копирование
// let p3 = p1;      // move — p1 больше не доступен
println!("{}", p1.name); // Ошибка! p1 перемещён

Почему Clone не может быть неявным? Потому что копирование String требует выделения памяти, которое может упасть (out-of-memory). Rust хочет, чтобы такие операции были явными, чтобы программист контролировал производительность и обработку ошибок.

Когда что использовать?

  • Copy — для простых, маленьких типов, которые дешево копировать: числа, логические значения, небольшие структуры из Copy-полей.
  • Clone — для типов, владеющих ресурсами (String, Vec, сложные структуры), или когда копирование — дорогая операция, которую нужно контролировать.

Пример из жизни

// Copy — можно неявно
fn print_coords(p: Point) {
    println!("{}, {}", p.x, p.y);
}

let p = Point { x: 10, y: 20 };
print_coords(p);  // копия
print_coords(p);  // ещё одна копия, p жив

// Clone — нужно явно
fn greet(person: Person) {
    println!("Hello, {}", person.name);
}

let alice = Person { name: "Alice".into(), age: 30 };
greet(alice.clone());  // явное копирование
// greet(alice);       // move — alice больше нельзя использовать

Почему Copy требует Clone?

Это архитектурное решение Rust: Copy — это подмножество Clone. Любой Copy-тип должен быть также Clone-типом, потому что копирование битов — это частный случай клонирования. На практике это означает, что #[derive(Copy, Clone)] — стандартная связка.

В итоге: если ваш тип — простой набор чисел, используйте Copy для удобства. Если есть динамические данные — используйте Clone и вызывайте его явно.

<<>>

Дополнительный взгляд: когда Copy опасен

Хотя Copy удобен, он может привести к неожиданным проблемам с производительностью. Представьте структуру с 1000 полями типа f64 — неявное копирование при каждом перемещении будет копировать 8 КБ данных, что может быть неочевидно для читающего код.

#[derive(Copy, Clone)]
struct BigData {
    values: [f64; 1024], // 8 КБ
}

fn process(data: BigData) { /* ... */ }

let d = BigData { values: [0.0; 1024] };
process(d);  // неявно копирует 8 КБ
process(d);  // ещё 8 КБ

С Clone такая ситуация невозможна — каждое копирование будет явным, и программист задумается: "А не передать ли мне ссылку вместо копии?".

Совет: Начинайте с Clone и только если вы уверены, что:

  • Тип маленький (меньше 32 байт, как правило)
  • Копирование дешево
  • Неявное копирование не затруднит чтение кода

...тогда добавляйте Copy. Помните: Copy — это обещание, что копирование безопасно и дешево. Не давайте его легкомысленно.