В чем разница между трейтами Copy и Clone в Rust?
Основное различие: семантика копирования против явного вызова
Главное отличие между Copy и Clone в Rust — это способ активации и гарантии безопасности. Давайте разберем на вашем примере.
Copy — неявное, дешевое копирование
Трейт Copy — это маркерный трейт (не содержит методов). Он говорит компилятору: "Этот тип можно безопасно скопировать простым побитовым копированием". Когда вы присваиваете значение или передаете его в функцию, Rust автоматически копирует данные, не перемещая оригинал. Это работает только для типов, которые хранятся на стеке и не владеют ресурсами (например, числа, bool, ссылки).
В вашем примере Point содержит только два i32 — простые числа. Поэтому #[derive(Copy, Clone)] разрешает неявное копирование. Когда вы пишете let p2 = p1;, Rust копирует байты, и p1 остается доступным.
// Copy срабатывает автоматически
let p1 = Point { x: 1, y: 2 };
let p2 = p1; // p1 не перемещается, а копируется
println!("p1: {:?}, p2: {:?}", p1, p2); // Оба живы
Clone — явное, потенциально дорогое копирование
Трейт Clone требует явного вызова метода .clone(). Он может выполнять глубокое копирование (например, клонировать содержимое Vec или String). Clone не гарантирует дешевизны — копирование Vec с миллионом элементов будет дорогим.
В вашем Line вы написали #[derive(Clone)], но не Copy. Почему? Потому что Rust не может автоматически определить, что Line — это Copy-тип. Даже если все поля являются Copy, компилятор требует явного указания. Без Copy присваивание let l2 = l1; вызывает move, а не копирование.
// Clone требует явного вызова
let l1 = Line { /* ... */ };
let l2 = l1.clone(); // Работает, но нужно написать .clone()
// let l2 = l1; // Ошибка: move! l1 больше не доступен
Правила выбора между Copy и Clone
- Используйте Copy, если: тип состоит только из Copy-полей (числа, bool, char, ссылки) и копирование — это просто копирование байтов. Примеры:
i32,f64,bool,&T, простые структуры вроде вашегоPoint. - Используйте только Clone, если: тип владеет ресурсами (памятью в куче, файловыми дескрипторами) или копирование должно быть явным для предотвращения случайных накладных расходов. Примеры:
String,Vec<T>,Box<T>. - Нельзя одновременно Copy и Clone, если: тип содержит
Drop-реализацию (например,Mutex). Copy и Drop взаимно исключают друг друга.
Когда писать свою реализацию Clone?
#[derive(Clone)] работает, когда все поля реализуют Clone. Но если вам нужно особое поведение (например, клонировать с изменением данных), пишите вручную:
struct UniqueId(u32);
impl Clone for UniqueId {
fn clone(&self) -> Self {
UniqueId(self.0 + 1) // Каждый клон получает новый ID
}
}
В итоге: Copy — это "бесплатное" неявное копирование для простых типов, Clone — это явный вызов для любых типов, включая сложные. В вашем примере просто добавьте Copy к Line, если уверены, что это безопасно:
#[derive(Copy, Clone)] // Теперь Line тоже Copy
struct Line {
start: Point,
end: Point,
}
<<Практические советы и типичные ошибки
Многие новички пытаются реализовать Copy для типов, которые этого не поддерживают. Например, для Vec или String:
// Ошибка! String не реализует Copy
let s = String::from("hello");
let t = s; // move! s больше не доступен
// println!("{}", s); // Ошибка компиляции
Если вам нужно скопировать String, используйте .clone():
let s = String::from("hello");
let t = s.clone(); // Явное копирование
println!("{} {}", s, t); // Оба живы
Еще один частый сценарий — работа с замыканиями. Если замыкание захватывает переменную по значению, а тип не реализует Copy, происходит move. Чтобы избежать этого, явно клонируйте:
let data = vec![1, 2, 3];
let closure = move || {
let data_clone = data.clone(); // Клонируем для использования
println!("{:?}", data_clone);
};
// data больше не доступен, так как был перемещен в замыкание
Простое правило: Если тип может быть скопирован простым присваиванием (let y = x;) и x остается действительным — это Copy. Во всех остальных случаях для копирования используйте .clone(). И помните: Copy — это подмножество Clone. Если тип реализует Copy, он обязан также реализовать Clone.
Ответов пока нет.
Войдите чтобы оставить ответ
Блоги