Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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! внутри TmpFile::drop() и запустите. Спросите класс: какие деструкторы выполнятся до аварийного завершения?

    После двойной паники Rust больше не гарантирует, что оставшиеся деструкторы выполнятся:

    • Некоторая очистка, уже начатая, всё ещё может завершиться (например, деструкторы полей значения, которое сейчас освобождается),
    • но всё, что запланировано позже на пути раскручивания, может быть пропущено полностью.
    • Поэтому мы говорим, что нельзя полагаться только на drop() для критически важной внешней очистки и нельзя предполагать, что двойная паника прерывает работу без выполнения каких-либо дальнейших деструкторов.
  • Некоторые языки запрещают или ограничивают исключения в деструкторах. В Rust можно вызвать панику в Drop::drop, но это почти никогда не хорошая идея, так как это может нарушить раскручивание стека и привести к непредсказуемой очистке. Лучше избегать этого, если нет очень конкретной необходимости, например в случае drop bomb.

  • Drop подходит для очистки ресурсов в пределах процесса, но он не подходит для гарантий того, что что-то произойдёт за пределами процесса (например, на локальном диске или в другом сервисе распределённой системы).

  • Например, удаление временного файла в drop() допустимо в учебном примере, но в настоящей программе всё равно понадобился бы внешний механизм очистки, например сборщик временных файлов.

  • Напротив, на drop() можно положиться при разблокировке мьютекса, так как это ресурс, локальный для процесса. Если drop() пропущен и мьютекс остаётся заблокированным, это не имеет долгосрочных последствий за пределами процесса.