Как сделать тип только перемещаемым (move), но не копируемым (Copy) в Rust?
Краткий ответ: да, тип по умолчанию является только перемещаемым
В Rust любой тип, который не реализует типаж Copy, автоматически становится только перемещаемым (move-only). Это фундаментальное правило: если тип не помечен как Copy, при присваивании или передаче в функцию происходит перемещение (move), а не копирование. Исходная переменная становится недействительной.
Ваш пример: почему это работает
В вашем коде структура MyType не реализует Copy (и даже Clone). Поэтому строка let b = a; — это перемещение. После неё a больше не владеет данными, и попытка использовать a.value приведёт к ошибке компиляции:
error[E0382]: borrow of moved value: `a.value`
--> src/main.rs:7:28
|
6 | let b = a;
| - value moved here
7 | println!("{}", a.value);
| ^^^^^^^ value borrowed here after move
Как явно запретить копирование
Если вы хотите быть уверены, что ваш тип никогда не будет скопирован (даже если кто-то позже попытается добавить derive(Copy)), вы можете использовать один из двух подходов:
- Стандартный способ — просто не реализуйте
Copy. Это уже даёт поведение move-only. Но если в структуре есть поля, которые сами реализуютCopy(какi32), компилятор может разрешить добавитьCopyк вашей структуре. Чтобы предотвратить это даже случайно, используйте второй подход. - Добавьте поле, которое не реализует
Copy. Например, включитеPhantomDataили тип-заглушку:
use std::marker::PhantomData;
struct UniqueResource {
id: i32,
// Это поле не имеет размера, но делает тип не-Copy
_not_copy: PhantomData<*const ()>,
}
fn main() {
let a = UniqueResource { id: 42, _not_copy: PhantomData };
let b = a; // move
// println!("{}", a.id); // Ошибка! a больше недействителен
}
Почему это работает?
PhantomData<*const ()> — это тип, который не реализует Copy (потому что сырые указатели не являются копируемыми по умолчанию). Компилятор видит, что структура содержит такое поле, и автоматически запрещает реализацию Copy для всей структуры. Даже если вы попытаетесь написать #[derive(Copy)] — компилятор выдаст ошибку.
Важные нюансы
- Типаж
Cloneне делает тип копируемым.Clone— это явное клонирование через метод.clone(). Вы можете реализоватьCloneдля move-only типа, и это не нарушит правила перемещения. Например:
#[derive(Clone)]
struct MyType {
value: i32,
// без Copy — только move
}
fn main() {
let a = MyType { value: 10 };
let b = a.clone(); // явное копирование — разрешено
let c = a; // перемещение — разрешено, но a теперь недействителен
}
- Типаж
Copy— это маркер. Он говорит компилятору: «Этот тип можно безопасно копировать побитово». Если вы его не реализуете, компилятор будет требовать явного перемещения или клонирования.
Проверка на практике
Вы можете проверить, реализует ли ваш тип Copy, с помощью вспомогательной функции:
fn is_copy<T: Copy>() {}
fn main() {
// is_copy::<MyType>(); // Ошибка, если MyType не Copy
}
Итог
Чтобы создать тип, который только перемещается и не копируется:
- Не добавляйте
derive(Copy). - Убедитесь, что все поля типа сами не реализуют
Copy(или используйтеPhantomDataдля защиты). - При желании добавьте
derive(Clone)для явного клонирования.
Это даст вам поведение, аналогичное String или Vec: при присваивании — перемещение, при явном вызове .clone() — копирование.
Альтернативный подход: использование типажа Drop для гарантии
Если вы хотите быть абсолютно уверены, что ваш тип никогда не будет скопирован (даже случайно), вы можете реализовать для него типаж Drop. Дело в том, что Rust запрещает одновременно реализовывать Copy и Drop для одного типа. Это логично: если у типа есть деструктор (Drop), то его нельзя просто так скопировать побитово, иначе деструктор может быть вызван дважды для одних и тех же данных.
struct UniqueId {
id: i32,
}
impl Drop for UniqueId {
fn drop(&mut self) {
println!("Уникальный ID {} уничтожен", self.id);
}
}
fn main() {
let a = UniqueId { id: 1 };
let b = a; // move — OK
// let c = a; // Ошибка! a перемещён
// Если попытаться добавить #[derive(Copy)] — ошибка компиляции
}
Этот метод особенно полезен для типов, управляющих ресурсами (файлы, сокеты, выделенная память), где двойное освобождение привело бы к катастрофе. Drop не только блокирует Copy, но и гарантирует выполнение очистки при завершении жизни объекта.
Недостаток: если ваш тип не требует деструктора, добавление Drop может быть избыточным. В таком случае лучше использовать PhantomData из первого ответа — он не добавляет накладных расходов и не меняет семантику.
Ответов пока нет.
Войдите чтобы оставить ответ