*STDRRには不用意にレイヤを追加しないほうがいい
以下のコードは「done.」だけ出力して終了する*1。
#!perl -w use strict; binmode *STDERR, ':encoding(foo)'; print join(' ', PerlIO::get_layers(*STDERR)), "\n"; END{ print "done.\n"; } __END__
これはbinmode()がdieするためだが,本来なら,:encodingに不正なエンコード名を与えても,レイヤの追加に失敗するだけでdieしたりはしないはずだ。また,普通はdieしたとしてもエラーメッセージを表示してから終了するはず。しかしなぜ何も表示せずに異常終了するのか。
それは追加するファイルハンドルが*STDERRだからだ。
binmode()の動作を追ってみる。まず,binmode()ルーチン*2は:encodingレイヤのインスタンスを作成し,*STDERRレイヤスタックにその未初期化の:encodingレイヤインスタンスを追加する。その後:encodingのイニシャライザが呼び出されるが,"foo"というエンコーディングは存在しないため,:encodingのイニシャライザは警告を発しようとする。そこで警告メッセージを*STDERRに出力しようとするが,*STDERRには初期化が終わっていない:encodingレイヤインスタンスが乗ったままなので,:encodingを介した警告メッセージの出力は失敗に終わる*3。
以上が沈黙のdieの流れである。*STDERRに警告を発する可能性のあるレイヤを追加するのは注意が必要だ。なお,上記のコードの場合,binmode()をeval{}で補足し,例外が発生した後binmode(*STDERR, ':pop')で初期化の終わっていない:encodingを取り除けば*STDERRの状態は回復する。