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: 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>, который позволяет понять, был ли дескриптор уже вручную обработан.