Ruby の最近の変更で間違って使われていることが多い ENV.dup
で例外が発生するようになり、 ENV.clone
で警告がでるようになりました。 問題なくこのままリリースされれば Ruby 3.1 からこの挙動になります。
問題点
gem-codesearch や github での検索で調べると ENV.dup
や ENV.clone
はテストで一時的に環境変数をまるごと保存しておいて戻したい、という用途で使われているようにみえました。 しかし、実際にはそういう動作はしていません。
ENV.dup
ENV
の特殊な挙動は特異メソッドで定義されていて、特異メソッドをコピーしない ENV.dup
は Object.new
とほぼ同義で、テスト後に ENV
を ENV.dup
の返り値に戻すと ENV#[]
や ENV#[]=
などがなく、元の ENV
のようには全く動かなくて意味がない状態でした。
そのため、 ENV.dup
の時点で例外になっても互換性に問題はなさそうということで Cannot dup ENV, use ENV.to_h to get a copy of ENV as a hash (TypeError)
という例外が発生するようになりました。
ENV.clone
ENV#[]
や ENV#[]=
は getenv(3)
や setenv(3)
のような OS の環境変数を直接扱うメソッドで、オブジェクトの中には情報を保存していません。
そのため、 ENV.clone
の返り値のオブジェクトからの操作でも OS の環境変数が書き換えられてしまい、 ENV
定数がどちらのオブジェクトを指すようにしても、環境変数の一時保存などはできません。
しかし、意図とは違うかもしれないとはいえ、動いているものが壊れることになるので、まずは警告になりました。
% ruby -we 'ENV.clone'
-e:1: warning: ENV.clone is deprecated; use ENV.to_h instead
正しい一時保存方法
例外や警告のメッセージにあるように、環境変数全部を一時的に保存しておきたいときは ENV.to_h
を使います。 そして、戻すときは ENV.replace
を使います。
% ruby -e 'env = ENV.to_h; ENV.clear; p ENV["LANG"]; ENV.replace(env); p ENV["LANG"]'
nil
"ja_JP.UTF-8"
まとめ
ENV
で間違った使われ方をすることが多い ENV.dup
と ENV.clone
の変更を紹介しました。
ENV
は他にも freeze
が Ruby 2.7 から組み込みで唯一例外になる (freeze
していても setenv(3)
できるため) など、誤解の元になる挙動は変更されてきた経緯があります。
この記事が ENV
の間違った使い方を減らす参考になれば幸いです。