frozen_string_literal
が入ったことでプログラムを調整していたら、気づいたことがありました。
frozen_string_literal
とは
ことは、Ruby 2.1で'str'.freeze
とコードに書いたときに、特別な最適化が入るようになったことに遡ります。この最適化を享受しようと、至る所の文字列をfreeze
しだすという混沌に陥ったこともあって、「Ruby 3.0からは、文字列リテラルはデフォルトでfreeze
状態にする」ということになりました。
ただ、いきなり3.0に移行すれば混乱も避けられないので、Ruby 2.3からは冒頭にコメントを書くことで、この「文字列リテラルのfreeze
」の挙動を変えられるようになりました。
-
# frozen_string_literal: true
→文字列リテラルはすべてfreeze
された状態になる -
# frozen_string_literal: false
→文字列リテラルはfreeze
されない
なお、この指定はファイル単位で有効で、そのファイルに書かれた文字列リテラルだけに有効です。
あぶり出された、おかしなプログラム
文字列でもなんでも、非破壊的な操作を繰り返すとコストが嵩むことがあって、JavaではStringBuilder
というString
生成用のオブジェクトが用意されていますが、Rubyの場合はString
自体が可変なので、別途用意はされていません。
そうなれば勢い、<<
で破壊的に連結、ということがよく行われるのですが、frozen_string_literal: true
としたところでエラーになったのを確認してみたら、なんと引数に与えた文字列に<<
で破壊的操作を加えていたのでした。
ActiveRecordオブジェクトのように、破壊的操作を行うことが念頭に置かれたオブジェクトもありますが、「文字列引数を渡したら、書き換わって返ってきた」というのは、多くの場合意図するような動作ではないでしょう。引数の文字列に直接<<
する、なんていう操作は、(うっかりやらかしているかもしれませんが)基本的に、やったらまずい部類です。
おまけ
Rubyのメソッドでは、最後にsome_method 50, foo: :bar, baz: 1
のような形でハッシュを渡すものが多いのですが、これの中で.delete
などの破壊的操作を行っていることが時折見受けられます。
上の例のようなbare hashであればその場で生成するので問題ないのですが、ふつうのHash
を渡せば引数のパースで変わってしまうし、frozenなのを渡せば破壊的に操作できなくてエラーになってしまいます(複数のメソッドに同じハッシュを渡すので、使いまわそうとして気づきました)。
こちらも、dup
するなどして対応が必要そうです。