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() を呼び出さなければならず、さらにエラー時の早期 return 経路でも呼び出さなければなりません。

  • ユーザーが 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 を返せない点に注意してください。失敗はすべて内部で処理するか、無視しなければなりません。標準ライブラリでは、Drop 内で FD を閉じる際のエラーは黙って破棄されます。実装は次を参照してください: https://doc.rust-lang.org/src/std/os/fd/owned.rs.html#169-196

  • Drop::drop はいつ呼び出されるのでしょうか?

    通常、main()file 変数がスコープを抜けるとき(return 時でも panic による場合でも)、drop() は自動的に呼び出されます。

    ファイルが別の関数にムーブされる場合(File::close() がこのケースです)、値がドロップされるのは main ではなく、その関数が return するときです。

    対照的に、C++ ではムーブ元の値に対しても元のスコープでデストラクタが実行されます。

  • デモ: read_to_end() の先頭に panic!("oops") を挿入して実行してください。アンワインド中でも drop() は実行されます。

さらに調べる

Drop トレイトには、もう 1 つ重要な制約があります。async ではないことです。

これは、デストラクタの中で await できないことを意味します。これは、ソケットやデータベース接続、あるいは別のシステムに完了を通知しなければならないタスクのような非同期リソースをクリーンアップするときによく必要になります。