Drop можно пропустить
Есть случаи, когда деструкторы не выполняются.
// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0
#[derive(Debug)]
struct OwnedFd(i32);
impl Drop for OwnedFd {
fn drop(&mut self) {
println!("OwnedFd::drop() called with raw fd: {:?}", self.0);
}
}
impl Drop for TmpFile {
fn drop(&mut self) {
println!("TmpFile::drop() called with owned fd: {:?}", self.0);
// libc::unlink("/tmp/file")
// panic!("TmpFile::drop() panics");
}
}
#[derive(Debug)]
struct TmpFile(OwnedFd);
impl TmpFile {
fn open() -> Self {
Self(OwnedFd(2))
}
fn close(&self) {
panic!("TmpFile::close(): not implemented yet");
}
}
fn main() {
let owned_fd = OwnedFd(1);
let file = TmpFile::open();
std::process::exit(0);
// std::mem::forget(file);
// file.close();
let _ = owned_fd;
}
-
Drop не гарантированно выполняется всегда. Есть несколько случаев, когда drop пропускается: программа может аварийно завершиться или выйти, значение с реализацией drop может утечь и т. п.
-
В версии, которая вызывает
std::process::exit,TmpFile::drop()никогда не выполняется, потому чтоexit()немедленно завершает процесс без возможности вызова методаdrop().- Можно предотвратить случайное использование
exit, запретив линтclippy::exit.
- Можно предотвратить случайное использование
-
Если удалить строку
std::process::exit(0), каждый методdrop()в этом простом случае выполнится по очереди. -
Попробуйте раскомментировать вызов
std::mem::forget. Как вы думаете, что произойдёт?mem::forget()забирает владение и «забывает» о значенииfile, не выполняя его деструкторDrop::drop(). Деструкторowned_fdвсё равно выполняется. -
Удалите вызов
mem::forget(), затем раскомментируйте вызовfile.close()под ним. Что вы ожидаете теперь?При настройке по умолчанию
panic = "unwind"стек всё равно раскручивается и деструкторы выполняются, даже если паника начинается вmain.- С
panic = "abort"деструкторы не выполняются.
- С
-
Последним шагом раскомментируйте
panic!внутриTmpFile::drop()и запустите. Спросите класс: какие деструкторы выполнятся до аварийного завершения?После двойной паники Rust больше не гарантирует, что оставшиеся деструкторы выполнятся:
- Некоторая очистка, уже начатая, всё ещё может завершиться (например, деструкторы полей значения, которое сейчас освобождается),
- но всё, что запланировано позже на пути раскручивания, может быть пропущено полностью.
- Поэтому мы говорим, что нельзя полагаться только на
drop()для критически важной внешней очистки и нельзя предполагать, что двойная паника прерывает работу без выполнения каких-либо дальнейших деструкторов.
-
Некоторые языки запрещают или ограничивают исключения в деструкторах. В Rust можно вызвать панику в
Drop::drop, но это почти никогда не хорошая идея, так как это может нарушить раскручивание стека и привести к непредсказуемой очистке. Лучше избегать этого, если нет очень конкретной необходимости, например в случае drop bomb. -
Drop подходит для очистки ресурсов в пределах процесса, но он не подходит для гарантий того, что что-то произойдёт за пределами процесса (например, на локальном диске или в другом сервисе распределённой системы).
-
Например, удаление временного файла в
drop()допустимо в учебном примере, но в настоящей программе всё равно понадобился бы внешний механизм очистки, например сборщик временных файлов. -
Напротив, на
drop()можно положиться при разблокировке мьютекса, так как это ресурс, локальный для процесса. Еслиdrop()пропущен и мьютекс остаётся заблокированным, это не имеет долгосрочных последствий за пределами процесса.