Perl5.14でeval内の$@の挙動が変わっていた

Try::LiteとException::Tinyで例外処理をやろうと導入していたのですが、どうやらネストさせてTry::Liteを使った場合に外側でうまく例外をキャッチ出来ないという現象が発生して悩んでました。

Try::Liteで外側で例外を取得出来なかった例

こんなコードです。

自分が使っているPerlのバージョンは5.14より古いものでしたので、ここにある通り外側で例外がキャッチできない状態でした。
die $@でも、$@->rethrowでもException::Tiny->throwでもダメでした。

Twitterで解決

その時には5.14以上で動くことは知らなくて悩んでいたところ、@hide_o_55さんが

と教えてくださり、試したところ確かにgistにある通り意図した通りに動きました。。

5.14からeval内の$@の挙動が変わった!?

というわけで5.14のどの変更が影響しているのかなぁと思ったところ、ここに書かれているのが影響してそうだなということがわかりました。

同様に eval の中の local $@ はスコープ内の例外を上書きすることは なくなりました。 以前は、巻き戻しによる $@ の復元によって、投げられた例外が 上書きされることがありました。 今では例外はとにかく eval に渡されます。 そのため、die の前の local $@ は安全です。

http://perldoc.jp/docs/perl/5.14.0/perl5140delta.pod#Exception32Handling


というわけでevalにして検証コードを書いてみました。evalの中でlocal $@ で値を入れてその後にdieしてみた動作です。

#!perl
use 5.010;

eval {
    local $@ = "foo";
    die "hoge";
};
say "throw Exception [$@]" if $@;

# perl 5.12 (何も出力されない)

# perl 5.14
throw Exception [hoge at try.pl line 14.
]

5.12ではevalを抜けた時に$@が巻き戻されて(?)、undefになってるんですかね...。ちなみにevalの前にlocal $@ = "init";などとしても結果は同じでした。

というわけで

5.14より前の環境でevalの中でlocal $@とやったり、Try::Liteをネストさせて使いたい場合は注意が必要かなと思いました。
Try::Liteが5.14より古い環境でもネストさせても動くようになると嬉しいので、pull req出来たらしたいなと思ったりしています。