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(())
}
-
Легко пропустить:
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 внутри деструктора, что часто нужно при очистке асинхронных ресурсов, таких как сокеты, соединения с базой данных или задачи, которые должны сообщить другой системе о завершении.
- Подробнее: https://rust-lang.github.io/async-fundamentals-initiative/roadmap/async_drop.html
- Экспериментальный трейт
AsyncDropдоступен в nightly: https://doc.rust-lang.org/nightly/std/future/trait.AsyncDrop.html