finallyの挙動を正しく理解する、returnとリソース解放の交差点

finallyとは、try/catchで例外の有無にかかわらず必ず実行されるブロックです。リソースの後始末に使いますが、中でreturnするとtry側の返り値を上書きします。

finallyは例外があってもなくても必ず通る

finally の一番の役割は「成功しても失敗しても必ず実行される」ことですね。ファイルハンドルやロックのような、確保したら必ず解放したいものの後始末に向いています。try の中で例外が飛んでも finally は通るので、解放漏れを防げます。

$fp = fopen('data.csv', 'r');
try {
    return $this->parse($fp); // 途中で例外が出ても
} finally {
    fclose($fp); // ここは必ず実行される
}

try の中に return があっても finally はその後に実行される、という点が肝で、ここを知らないと解放タイミングを誤解しがちだと思います。

finallyの中のreturnはtryのreturnを上書きする

ここが落とし穴なんですが、finally の中で return を書くと、try や catch で返そうとしていた値を上書きしてしまいます。しかも投げられていた例外まで握りつぶすので、意図しない挙動になりやすいです。finally では基本的に return しない、と決めておくのが安全だと思います。

function test(): string
{
    try {
        return 'try';
    } finally {
        return 'finally'; // これが勝つ。'try' は捨てられる
    }
}
echo test(); // finally

返り値を評価してからfinallyが走る順番

もう少し細かい話をすると、try の return 式は先に評価されて値が確定し、その後 finally が実行され、最後にその確定済みの値が返ります。なので finally 内で変数をいじっても、既に確定した返り値は変わりません。ここも直感と少しずれるところですね。

function counter(): int
{
    $n = 1;
    try {
        return $n; // ここで 1 が確定
    } finally {
        $n = 99; // 返り値には影響しない
    }
}
echo counter(); // 1

まとめ

finally は後始末を確実に走らせるための仕組みで、リソース解放に使うのが本筋だと思います。ただし finally の中で return すると try 側の返り値も例外も上書きしてしまうので、そこは避ける。返り値は try 時点で確定して finally はその後に走る、という順番を押さえておけば、挙動に驚かされることは減る気がします。

よくある質問

Q. finallyは必ず実行されますか?
A. はい。tryの中で例外が飛んでも、returnがあっても、finallyは必ず通ります。リソース解放に向いています。

Q. finallyの中でreturnしてもいいですか?
A. 避けたほうが安全です。try側の返り値も、投げられていた例外も上書きして握りつぶしてしまいます。

Q. try内でreturnした値をfinallyで変えられますか?
A. 変えられません。return式が先に評価されて値が確定し、その後finallyが走るので、変数をいじっても返り値は変わりません。

類似投稿

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です