5
1

More than 1 year has passed since last update.

[Ruby]nil.to_sがfrozen stringを返すので、対策を色々考えてみる

Last updated at Posted at 2023-03-09

Rubyでnil.to_sと叩くといわゆる空文字("")が返却されます。
しかし、Ruby 2.6以前と2.7以降では微妙に扱いが違い、2.7以降ではfrozen stringが返るようになりました。

これはRuby2.7のリリースノートにも書かれています。

Module#name, true.to_s, false.to_s nil.to_s は常にfrozenな文字列を返すようになりました。返された文字列は常に同じオブジェクトとなります。 [Experimental] [Feature #16150]

なので、下記のようなコードの場合、変数sはnilが入るとエラーになります。

def gsub_hoge_fuga(s)
  s.to_s.gsub!('hoge', 'fuga') # s = nilだと can't modify frozen String: "" (FrozenError)
end

では、どのようにしてエラーを回避するかを上記の例をもとに考えてみましょう。

そもそもnilのときに関数を呼ばないようにする

これは単純ですね。

replaced_string = gsub_hoge_fuga(s) unless s.nil? # ActiveSupportが入ってるならblank?をとかempty?とかでもいいかも

def gsub_hoge_fuga(s)
  s.to_s.gsub!('hoge', 'fuga') # s = nilだと can't modify frozen String: "" (FrozenError)
end

nilの時エラーが起きるとわかっているなら、nil入れなければいいじゃないというストロングスタイルです。
自分も一人で趣味開発してる時は、何も考えずにこうしてしまうかもしれません。
書いているコードの用途にもよりますが、他の人がこの関数を使おうとした時にnilが混入するリスクを減らすために、yardを書いてあげるなどの動きをしておくともっと良いかもしれません。

dupを使う

def gsub_hoge_fuga(s)
  s = s.to_s.dup
  s.gsub!('hoge', 'fuga')
end

dupはオブジェクトの参照値をコピーして新しくオブジェクトを作成するメソッドですが、dupで生成されたオブジェクトはfreezeされていません。
なのでdupで生成されたオブジェクトには心置きなくgsub!をすることができます。

irb(main):025:0> s = nil.to_s
=> ""
irb(main):026:0> s.frozen?
=> true
irb(main):027:0> t = s.dup
=> ""
irb(main):028:0> t.frozen?
=> false

そもそもgsub!ではなくgsubを使う

def gsub_hoge_fuga(s)
  s.to_s.gsub('hoge', 'fuga')
end

そもそも今回のケースだと、置換して終わりなので破壊的メソッドであるgsub!を無理に使わなくてもgsubで事足りる場合も多いでしょう。
gsubとgsub!の違いを理解して使いわけをするようにしましょう。

early returnする

def gsub_hoge_fuga(s)
  return '' if s.nil?
  s.to_s.gsub!('hoge', 'fuga')
end

このケースだとnilだけ例外処理を挟んでしまうのもいいでしょう。gsubしても置換は起きないので、''を返すことでRuby2.6以前の動作になります。

+@を使う

def gsub_hoge_fuga(s)
  (+s.to_s).gsub!('hoge', 'fuga')
end

+以降に記載されたオブジェクト(上記の例だとs.to_s)がfreezeされているかを検査し、freezeされている場合、複製を返してくれます。
カッコがないと、s.to_s.gsub!まで実行されてからfreezeかどうかを判定する、という処理になりエラーになるので注意しましょう。

まとめ

この記事を書いたきっかけはruboty-slack_rtmというgemに、同様のエラーでひっかかってパッチを送ったことでした。
一つのエラーを解決するためにサッと並べただけでもこれだけの解決方法があるあたり、Rubyは相当柔軟な言語だなと感じました。
また、上にあげた方法以外にもあると思うので、その時その時でベストエフォートを考えるようにしたいです。

5
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1