s
に String か nil が入っているとき,nil を空文字列扱いにして処理したい場合があるよね。
その場合,s.to_s
と s || ''
とどっちがいいんだろう?というお話。
nil.to_s
は空文字列を返すのでどっちでも同じじゃん,と思いがちだけど,微妙な違いがある。文字コードが違うのだ。
p ''.to_s.encoding #=> #<Encoding:UTF-8>
p nil.to_s.encoding #=> #<Encoding:US-ASCII>
たいがい,この違いは気にしなくてもいい。UTF-8 が「ASCII 互換エンコーディング」なので,US-ASCII の文字列との間で比較や結合もできるし,index
で互いに検索したり,split
や scan
の引数に使っても問題ない。
ただ,もし UTF-8 でしか動かない処理に投入するのであれば,s.to_s
は使ってはいけないことになる。
実際,そういう目に遭った。UTF-8 と CP932 の両方に対応した文字列処理メソッドで,encoding の値で場合分けしていたところ,想定外だった US-ASCII の空文字が来ちゃって死んだ。
2021-08-25 追記:Ruby 2.7 で nil.to_s は frozen
Ruby 2.7.0 で,nil.to_s
にわずかな非互換性が導入された(ことを Ruby 3.0 時代になってから思い出した)。
このバージョンからは nil.to_s
が凍結された空文字列を返すようになったのだ。しかも,何回実行しても常に同じオブジェクトが返される。
# Ruby 2.6 の場合
empty_strings = Array.new(3){ nil.to_s }
p empty_strings.first.frozen? # => false
p empty_strings.map{ |s| s.object_id }.uniq.count # => 3
# Ruby 2.7 以降の場合
empty_strings = Array.new(3){ nil.to_s }
p empty_strings.first.frozen? # => true
p empty_strings.map{ |s| s.object_id }.uniq.count # => 1
一方,文字列リテラル ""
は,マジックコメントで frozen-string-literal
を true
にしていない限り,凍結されていない空文字列を返す。そして評価されるたびに新たな String オブジェクトが作られる。
よって,s.to_s
と s || ""
のどちらがよいかは,s
が nil
のときに凍結された空文字列が欲しいのかどうかも検討することになる。