Perlには、変数宣言の3つの主要なタイプがあります:my、our、state。
sub example_state { state $counter = 0; $counter++; return $counter; } for (1..3) { print example_state(), "\n"; # 1、2、3が表示されます }
正しい使用法:
my — グローバルアクセスの理由がない限り、変数にはほぼ常に好ましい選択です。our — モジュール間で変数をエクスポートする必要がある場合に使用します。state — 変数が関数の呼び出し間で「記憶」する必要がある場合(例えば、呼び出しのカウンタ)に使用します。ourで宣言された変数は、myと同様にクロージャー内でキャプチャされることができますか?
通常は「はい、便利です」と答えますが、それは正しくありません。our変数はパッケージに対してグローバルであり、クロージャーは実際には作成時にその値をバインドするのではなく、グローバルな名前を介してアクセスしています。したがって、クロージャーの外部でその「値」が変更されると、すべてのクロージャーに影響を与えます!
our $x = 10; my $closure = sub { return $x; }; $x = 42; print $closure->(); # 10ではなく42を返します
物語
大規模なログ解析スクリプトでは、異なる関数から「便利にアクセスする」ためにourを使用して変数が宣言されていました。その結果、ある関数でこれらの変数が変更されると、別の関数で予期しない動作が発生し、ログの並行処理が壊れました。
物語
関数の呼び出し間で値を保持する必要がある変数(例えば、再帰的な巡回内のカウンタ)にmyを使用することで、各呼び出し時に値がリセットされ、不正な巡回ロジックが発生しました。これをstateに置き換えることで状況が改善されました。
物語
インポートされるモジュール内でのourの誤用(Exporterを介さずに変数をエクスポートすること)は、同じ名前の変数を使用する2つの異なるモジュールを接続する際に名前の衝突を引き起こしました。その結果、データがあるコンテキストから別のコンテキストに「飛び移る」ことになりました。