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<()> {
        // `Handle` を取得するには、`Option` を経由する必要があります。
        // そうして初めて使えるようになります。
        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(())
}
  • この例では、Drop 実装の中で内部の Handle に対して close を呼び出したいのですが、 closeHandle の所有権を必要とします。通常はこれはできません。なぜなら、 drop では File オブジェクトの所有権を受け取れないため、 フィールドから値をムーブできないからです。

  • HandleOption で包むことで、可変参照を通してフィールドから値をムーブ できるようになります。

  • 主な欠点は使い勝手です。Option にすると、論理的には None が起こりえない場所でも、 SomeNone の両方のケースを扱わなければなりません。Rust の型システムでは File とその Handle の間のその関係を表現できないため、 両方のケースを手動で処理します。

さらに調べる

Option の代わりに ManuallyDrop を使うこともできます。これは、Rust が値に対して Drop を呼び出さないようにすることで 自動破棄を抑制するもので、後始末は自分で処理しなければなりません。

前のスライドのscopeguard の例では、 ManuallyDropOption を置き換えることで、値が常に存在するはずの場所で None を扱わずに済む方法を示しています。

このような設計では通常、ManuallyDrop<Handle> の隣に別のフラグを置いて 破棄状態を追跡し、それによってハンドルがすでに手動で消費されたかどうかを 把握できます。