Допустимо ли в безопасном Rust намеренно не освобождать значение, и чем это отличается от нарушения памяти?
Да, безопасный Rust допускает намерительную утечку значения: std::mem::forget забирает значение во владение и не запускает его деструктор. Это может навсегда удержать память или внешний ресурс, но само по себе не нарушает memory safety: не возникает обращения к освобождённой памяти, двойного освобождения или недействительной ссылки.
Модель владения Rust обычно связывает завершение области видимости с вызовом Drop, чтобы ресурсы освобождались детерминированно без сборщика мусора. Однако безопасность памяти не требует гарантировать освобождение каждого ресурса в любой момент.
Поэтому язык различает две категории проблем: нарушение правил доступа к памяти является ошибкой безопасности, а утечка ресурса — проблемой корректности, производительности или доступности. Безопасный API может разрешать утечки, если это упрощает реализацию низкоуровневых механизмов и не создаёт невалидных ссылок.
Иногда значение владеет памятью, файловым дескриптором, блокировкой или другим ресурсом, а вызов его Drop в конкретный момент нежелателен. Простое перемещение значения в другое место может быть невозможно, а принудительное освобождение — преждевременным.
Неверное решение может привести к утечке памяти, исчерпанию дескрипторов, невыполненному сбросу буферов или неосвобождённой блокировке. При этом сама утечка не даёт права читать значение после его уничтожения: forget не делает объект доступным, он лишь предотвращает его финализацию.
std::mem::forget принимает T по значению. Передача значения означает перемещение владения в функцию, после чего функция намеренно не возвращает его и не запускает Drop.
В этом примере сообщение не выводится: значение забыто, поэтому его деструктор не вызывается. Память, занимаемая значением, становится недоступной для программы, а внешний ресурс, если он был бы внутри Resource, мог бы остаться занятым до завершения процесса или навсегда.
forget не обходит проверку заимствований. Для передачи значения по владению всё ещё требуется иметь право переместить его: активная ссылка или отсутствие владения могут запретить вызов на этапе компиляции.
Важно, что Rust вообще не гарантирует выполнение Drop во всех сценариях: процесс может быть аварийно завершён, а значения могут образовать циклическое владение через Rc. Поэтому небезопасный код не должен считать вызов деструктора абсолютной гарантией. Если ресурс критичен, его освобождение нужно проектировать явно и не полагаться только на финализацию.
Для более контролируемого управления обычно применяют ManuallyDrop<T>, но он требует аккуратно отслеживать, был ли деструктор вызван. Неправильное ручное управление может привести к двойному освобождению или использованию уже уничтоженного значения; такие последствия особенно опасны внутри unsafe-кода.
Низкоуровневый код передаёт объект-владелец в компонент, который должен продолжить управлять ресурсом самостоятельно. Рассматривались три варианта: вызвать drop немедленно, вернуть объект обратно или намеренно забыть его.
Немедленный drop освобождает ресурс слишком рано. Возврат объекта требует существования подходящего владельца и усложняет интерфейс. mem::forget подходит только тогда, когда ресурс действительно передан внешней системе, которая гарантированно возьмёт его под управление; иначе это обычная утечка.
Выбранное решение должно явно документировать нового владельца ресурса и условия его освобождения. Если такой гарантии нет, предпочтительнее сохранить объект в структуре-владельце или использовать специальный тип, отражающий передачу ответственности. Результат — отсутствие двойного освобождения, но только при доказанном существовании другого механизма очистки.
mem::forget?Нет. Владение передано функции, а значение намеренно не возвращено и не сохранено в доступном месте. В безопасном Rust нет операции, которая восстановила бы забытый объект; попытка обращаться к памяти через указатель после такого действия потребовала бы unsafe и могла бы нарушить правила памяти.
mem::forget память, если значение содержит несколько полей-владельцев?Нет. Поскольку деструктор самого значения не запускается, автоматически не уничтожаются и его поля, которые должны были бы освобождаться при обычном уничтожении. Это может привести к утечке всей цепочки принадлежащих ресурсов, хотя ссылки на них не становятся допустимыми для дальнейшего использования.
Drop?Потому что Drop не является гарантированным механизмом завершения программы: возможны mem::forget, циклы владения, аварийное завершение процесса и другие сценарии, при которых деструктор не выполняется. Если корректность unsafe-абстракции зависит от обязательного освобождения ресурса, она должна обеспечивать это другим проверяемым механизмом, а не предполагать, что область видимости всегда завершится обычным образом.