Drop: Option
// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0
struct File(Option<Handle>);
impl File {
fn open(path: &'static str) -> std::io::Result<Self> {
Ok(Self(Some(Handle { path })))
}
fn write(&mut self, data: &str) -> std::io::Result<()> {
// Нужно пройти через `Option`, чтобы получить `Handle`,
// прежде чем мы сможем его использовать.
let handle = self.0.as_ref().unwrap();
println!("write '{data}' to file '{}'", handle.path);
Ok(())
}
}
impl Drop for File {
fn drop(&mut self) {
let handle = self.0.take().unwrap();
handle.close();
}
}
struct Handle {
path: &'static str,
}
impl Handle {
fn close(self) {
println!("Closing {}", self.path);
}
}
fn main() -> std::io::Result<()> {
let mut file = File::open("foo.txt")?;
file.write("hello")?;
Ok(())
}
-
В этом примере мы хотим вызвать
closeдля внутреннегоHandleв нашей реализацииDrop, ноcloseтребует владенияHandle. Обычно это невозможно, потому что вdropмы не получаем владение объектомFile, и поэтому не можем переместить значение из поля. -
Обёртывание дескриптора в
Optionдаёт нам способ переместить значение из поля через изменяемую ссылку. -
Главный недостаток — эргономика.
Optionзаставляет обрабатывать и вариантSome, и вариантNone, даже в местах, где по логикеNoneвозникнуть не может. Система типов Rust не может выразить связь междуFileи егоHandle, поэтому мы вынуждены обрабатывать оба случая вручную.
Для дальнейшего изучения
Вместо Option можно использовать ManuallyDrop, который подавляет автоматическое уничтожение: Rust не вызывает Drop для значения, поэтому очистку нужно выполнять самостоятельно.
Пример с scopeguard на предыдущем слайде показывает, как ManuallyDrop может заменить Option, чтобы не обрабатывать None там, где значение должно существовать всегда.
В таких решениях состояние освобождения обычно отслеживают отдельным флагом рядом с ManuallyDrop<Handle>, который позволяет понять, был ли дескриптор уже вручную обработан.