Что мешает типу, реализующему Drop, одновременно реализовать Copy?
Тип с Drop нельзя сделать Copy, потому что неявное копирование создало бы несколько значений, каждое из которых считалось бы владельцем одного ресурса. При завершении их областей видимости деструктор мог бы выполниться несколько раз для одного ресурса, что привело бы к двойному освобождению или другому нарушению правил безопасной памяти.
Copy означает побитовое неявное копирование без перемещения исходного значения, а Drop требует единственного контролируемого владельца, отвечающего за освобождение ресурса.
В системном программировании значения часто владеют внешними ресурсами: памятью, файловыми дескрипторами, сокетами или блокировками. Без автоматического контроля копирование такого значения может привести к нескольким владельцам одного ресурса и неопределённому поведению при его освобождении.
Rust решает эту проблему без сборщика мусора: владение проверяется во время компиляции, а освобождение выполняется детерминированно. Разделение между Copy и Drop не позволяет случайно совместить безопасное неявное дублирование значения с уникальным управлением ресурсом.
Предположим, структура содержит идентификатор ресурса, а её Drop освобождает ресурс по этому идентификатору. Если такая структура будет Copy, присваивание или передача в функцию создаст второе значение, не изменяя первое.
Оба значения затем могут выйти из области видимости независимо. Компилятор не сможет рассматривать их как два независимых ресурса: внутри они ссылаются на один и тот же ресурс, поэтому вызов Drop для каждого значения может дважды закрыть дескриптор или освободить одну и ту же память.
Copy применяется к типам, безопасное копирование которых не требует специальной логики. После копирования исходное значение остаётся доступным, а новая переменная получает независимую копию битового представления. Поэтому Copy подходит для чисел, bool, символов и составных типов, все поля которых также безопасно копируются.
Drop задаёт код, который должен выполниться при уничтожении значения. Для ресурсоёмкого типа важно, чтобы существовал один логический владелец, а перемещение передавало ответственность этому владельцу, не создавая второго владельца.
Именно поэтому компилятор запрещает реализацию Copy для типа с Drop, включая попытку получить Copy через derive:
Такой код не компилируется: тип одновременно требует семантику Copy и имеет пользовательский деструктор. При этом Drop не запрещает Clone: Clone вызывается явно и может создать действительно новый ресурс, например через системную операцию дублирования дескриптора.
Важное следствие: поле вроде u32 само по себе копируемо, но структура, которая использует это число как владелец внешнего ресурса, не становится безопасной для Copy. Безопасность определяется семантикой всего типа, а не только типами его полей.
Нужно представить файловый дескриптор операционной системы в структуре Rust. Вариант с Copy кажется удобным: дескриптор можно передавать без перемещений. Однако две копии структуры будут содержать один числовой дескриптор, и обе попытаются закрыть его в Drop.
Вариант с обычным владением и перемещением безопасен: после передачи дескриптора прежняя переменная недоступна, а освобождение выполняет ровно один владелец. Недостаток — нельзя неявно использовать дескриптор в нескольких местах.
Вариант с Clone позволяет явно выбрать семантику. Если операционная система поддерживает дублирование дескриптора, clone может создать новый независимый дескриптор с отдельным временем жизни. Если нужно разделить одного владельца между компонентами, можно использовать совместное владение, например Arc, но это добавляет счётчик ссылок и стоимость синхронизации или управления временем жизни.
Практически выбирают обычное перемещение для уникального ресурса, явный Clone для настоящего дублирования и Arc для совместного владения. Запрет сочетания Copy и Drop предотвращает наиболее опасный вариант — неявное дублирование ресурса без явного решения разработчика.
Можно ли реализовать Clone для типа с Drop?
Да. Clone — явная операция, поэтому её реализация может корректно создать новый ресурс либо осознанно скопировать только данные, если это безопасно. Например, для файлового дескриптора clone может вызвать операцию ОС по дублированию дескриптора. В отличие от Copy, Clone не происходит автоматически при присваивании или передаче аргумента.
Почему простого запрета копирования числового поля недостаточно?
Потому что опасен не сам u32, а смысл значения. Число может быть обычными данными, а может быть идентификатором уникального внешнего ресурса. Rust не может вывести такую семантику из типа числа, поэтому ответственность за модель владения лежит на обёртке: она не должна реализовывать Copy, если её деструктор освобождает ресурс.
Что произойдёт при перемещении типа с Drop?
Перемещение не вызывает Drop для исходной переменной в момент передачи. Владение переходит новому значению, а исходное становится недоступным для использования. Деструктор выполнится позже для нового владельца при выходе из его области видимости, поэтому ресурс освобождается один раз.