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

RAII: трейт Drop

RAII (Resource Acquisition Is Initialization, «получение ресурса — это инициализация») связывает время жизни ресурса со временем жизни значения.

Rust использует RAII для управления памятью, а трейт Drop позволяет распространить это на другие ресурсы, например дескрипторы файлов или блокировки.

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

pub struct File(std::os::fd::RawFd);

impl File {
    pub fn open(path: &str) -> Result<Self, std::io::Error> {
        // [...]
        Ok(Self(0))
    }

    pub fn read_to_end(&mut self) -> Result<Vec<u8>, std::io::Error> {
        // [...]
        Ok(b"example".to_vec())
    }

    pub fn close(self) -> Result<(), std::io::Error> {
        // [...]
        Ok(())
    }
}

fn main() -> Result<(), std::io::Error> {
    let mut file = File::open("example.txt")?;
    println!("content: {:?}", file.read_to_end()?);
    Ok(())
}
This slide and its sub-slides should take about 110 minutes.
  • Легко пропустить: file.close() никогда не вызывается. Спросите класс, заметили ли они это.

  • Чтобы корректно освободить дескриптор файла, file.close() нужно вызвать после последнего использования, а также на путях раннего возврата при ошибках.

  • Вместо того чтобы полагаться на пользователя, который вызовет close(), мы можем реализовать трейт Drop, чтобы освобождать ресурс автоматически. Это связывает очистку со временем жизни значения File.

    #![allow(unused)]
    fn main() {
    // Copyright 2025 Google LLC
    // SPDX-License-Identifier: Apache-2.0
    
    impl Drop for File {
        fn drop(&mut self) {
            // libc::close(...);
            println!("file descriptor was closed");
        }
    }
    }
  • Обратите внимание, что Drop::drop() не может вернуть Result. Любые сбои нужно обрабатывать внутри или игнорировать. В стандартной библиотеке ошибки при закрытии FD внутри Drop молча отбрасываются. См. реализацию: https://doc.rust-lang.org/src/std/os/fd/owned.rs.html#169-196

  • Когда вызывается Drop::drop?

    Обычно, когда переменная file в main() выходит из области видимости (при возврате или из-за паники), drop() вызывается автоматически.

    Если файл перемещается в другую функцию (как в случае с File::close()), значение освобождается, когда эта функция возвращает управление, а не в main.

    Для сравнения: C++ выполняет деструкторы в исходной области видимости даже для значений, из которых было выполнено перемещение.

  • Демонстрация: вставьте panic!("oops") в начало read_to_end() и запустите. drop() всё равно выполнится во время раскручивания стека.

Для дальнейшего изучения

У трейта Drop есть другое важное ограничение: он не async.

Это значит, что нельзя использовать await внутри деструктора, что часто нужно при очистке асинхронных ресурсов, таких как сокеты, соединения с базой данных или задачи, которые должны сообщить другой системе о завершении.