Ruby の最近の変更で間違って使われていることが多い ENV.dup で例外が発生するようになり、 ENV.clone で警告がでるようになりました。 問題なくこのままリリースされれば Ruby 3.1 からこの挙動になります。

問題点

gem-codesearch や github での検索で調べると ENV.dupENV.clone はテストで一時的に環境変数をまるごと保存しておいて戻したい、という用途で使われているようにみえました。 しかし、実際にはそういう動作はしていません。

ENV.dup

ENV の特殊な挙動は特異メソッドで定義されていて、特異メソッドをコピーしない ENV.dupObject.new とほぼ同義で、テスト後に ENVENV.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.dupENV.clone の変更を紹介しました。

ENV は他にも freeze が Ruby 2.7 から組み込みで唯一例外になる (freeze していても setenv(3) できるため) など、誤解の元になる挙動は変更されてきた経緯があります。

この記事が ENV の間違った使い方を減らす参考になれば幸いです。

参考

Disqus Comments

Kazuhiro NISHIYAMA

Ruby のコミッターとかやってます。 フルスタックエンジニア(って何?)かもしれません。 About znzに主なアカウントをまとめました。

znz znz


Published