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

デストラクターでの終了処理

説明

Rust は finally ブロックに相当するもの、つまり関数がどのように終了しても 実行されるコードを提供していません。代わりに、オブジェクトのデストラクターを 使って、終了前に必ず実行しなければならないコードを実行できます。

fn baz() -> Result<(), ()> {
    // 何らかのコード
}

fn bar() -> Result<(), ()> {
    // これらは関数内で定義する必要はありません。
    struct Foo;

    // Foo のデストラクターを実装します。
    impl Drop for Foo {
        fn drop(&mut self) {
            println!("exit");
        }
    }

    // 関数 `bar` がどのように終了しても、_exit のデストラクターは実行されます。
    let _exit = Foo;
    // `?` 演算子による暗黙の return。
    baz()?;
    // 通常の return。
    Ok(())
}

動機

関数に複数の return ポイントがある場合、終了時にコードを実行することは 難しく、繰り返しが多くなります(したがってバグが入りやすくなります)。 これは、マクロによって return が暗黙的になる場合に特に当てはまります。 一般的な例は ? 演算子です。これは結果が Err の場合に return しますが、 Ok の場合は処理を継続します。? は例外処理の仕組みとして使われますが、 Java(finally を持つ)とは異なり、通常の場合と例外的な場合の両方で コードを実行するようにスケジュールする方法はありません。 panic も関数を早期に終了させます。

利点

デストラクター内のコードは(ほぼ)常に実行されます。panic、早期 return などに 対応できます。

欠点

デストラクターが実行されることは保証されていません。たとえば、関数内に 無限ループがある場合や、関数の実行が終了前にクラッシュする場合です。 すでに panic しているスレッド内でさらに panic が発生した場合にも、 デストラクターは実行されません。したがって、終了処理が行われることが 絶対に不可欠なファイナライザーとして、デストラクターに依存することはできません。

このパターンは、気づきにくい暗黙的なコードを導入します。関数を読んでも、 終了時に実行されるデストラクターがあることは明確には分かりません。 これにより、デバッグが難しくなることがあります。

終了処理のためだけにオブジェクトと Drop impl を必要とするのは、 ボイラープレートが多くなります。

議論

ファイナライザーとして使うオブジェクトを正確にどのように保持するかには、 いくつか微妙な点があります。そのオブジェクトは関数の終わりまで生存させ、 その後で破棄しなければなりません。オブジェクトは常に値または一意に所有された ポインター(例: Box<Foo>)でなければなりません。共有ポインター(Rc など)を 使うと、ファイナライザーが関数のライフタイムを超えて生存し続ける可能性があります。 同様の理由から、ファイナライザーは move したり return したりすべきではありません。

ファイナライザーは変数に代入しなければなりません。そうしないと、スコープを 抜けるときではなく、即座に破棄されます。その変数がファイナライザーとしてのみ 使われる場合、変数名は _ で始めなければなりません。そうしないと、 ファイナライザーが一度も使われていないとコンパイラーが警告します。ただし、 変数名を接尾辞なしの _ にしてはいけません。その場合は即座に破棄されます。

Rust では、オブジェクトがスコープを抜けるとデストラクターが実行されます。 これは、ブロックの終わりに到達した場合、早期 return がある場合、または プログラムが panic した場合のいずれでも発生します。panic 時には、Rust は スタックを巻き戻し、各スタックフレーム内の各オブジェクトに対してデストラクターを 実行します。そのため、呼び出し中の関数で panic が発生した場合でも、 デストラクターは呼び出されます。

巻き戻し中にデストラクターが panic した場合、取るべき適切な動作はないため、 Rust はそれ以上のデストラクターを実行せずに、ただちにスレッドを中止します。 これは、デストラクターの実行が絶対に保証されているわけではないことを意味します。 また、リソースが予期しない状態のまま残る可能性があるため、デストラクター内で panic しないように特別な注意を払わなければならないことも意味します。

関連項目

RAII ガード.