Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Rubyの文字列連結に「#+」ではなく式展開「#{}」を使うべき理由

更新情報

  • 初版: 2016/09/05
  • 更新: 2021/07/29

こんにちは、hachi8833です。

rubystrconcat

Slackでmorimorihogeさんが投げてくれたアドバイスのメモを残します。上のスクショはGitLabのmerge requestにmorimorihogeさんがコメントしたものです。

Rubyでの文字列連結メソッド#+#{}は同じではない!

Rubyの文字列連結メソッド#+#{}は、通常それぞれ次のように使われます。

str2 = "おいしい"

str1 = str2 + "店"  #=> 「#+」は変数同士や変数と文字列リテラルを連結する
str1 = "#{str2}店"  #=> 「#{}」は" "の中で変数内の文字列を取り出す(式展開)
                    #上のどちらも出力は「おいしい店」になる

後者の式展開(interpolation)は変数展開や文字列補間などと呼ばれることもあり、RubyやPythonなどのモダンな言語で文字列内に変数の値を置くときに使われる定番の手法です(追伸: JavaScriptではtemplate literalと呼ばれています)。

参考: 文字列補間 - Wikipedia

Rubyの文字列連結は上の例のどちらの方法を使っても同じになりそうな気がしますが、実は同じになるとは限りません

次に挙げる理由から、Rubyでは基本的に後者の式展開「#{}」を使うことをおすすめします。

理由1: 式展開なら自動で#to_sが効く

式展開#{}の変数は、出力時に自動的に#to_sが行われます。#to_sは文字列でない値を文字列に変換するメソッドであり、しかも値がnilの場合には空文字""を出力してくれるので、余分なエラーが発生しません。

理由2: 式展開の#to_sはほとんどのオブジェクトで使える

#to_sメソッドはObjectクラスにあります。言うまでもなくRubyではすべてのものがオブジェクトなので、#to_sメソッドはほぼどんなオブジェクトに対してもエラーなしで確実に実行できます。

「ほぼ」と書いたのは、Ruby 1.9以降の継承リストのルートにあるのはObjectクラスではなくBasicObjectだからです(クラスの継承リスト: Object < Kernel < BasicObject)。BasicObjectクラスにはほとんどメソッドがなく、さすがに#to_sはエラーになります(使う人はいないと思いますが)。

理由3:#+で連結すると順序によって結果が異なることがある

文字列連結であればどんな順序であろうと挙動が変わらないように思えますが、変数と文字列を#+で連結すると、変数の内容によって以下のような問題が生じる可能性があります

変数がもしFixnumだったら:

[1] pry(main)> 1 + 'hoge'
TypeError: String can't be coerced into Fixnum from (pry):1:in `+'
[2] pry(main)> 'hoge' + 1
TypeError: no implicit conversion of Fixnum into String from (pry):2:in `+'

変数がもしnilだったら:

[3] pry(main)> 'hoge' + nil
TypeError: no implicit conversion of nil into String from (pry):3:in `+'
[4] pry(main)> nil + 'hoge'
NoMethodError: undefined method `+' for nil:NilClass from (pry):4:in `<main>'

Rubyの#+で文字列と変数を結合すると、当初は正常に動作したとしても後で思わぬトラップを踏むかもしれません。

#+による文字列連結を使うなら、少なくとも変数を先行させないようにし、変数に文字列以外の値が使われないよう注意する必要があります。

しかし式展開#{}ならそうした気遣いはまったく不要になります。実際、前述の#+の問題はすべて式展開#{}で正常に動作します。

まとめ

変数を含む文字列を出力する場合は、基本的に+よりも式展開#{}を使うことをおすすめします。ただしユーザー入力を文字列に出力するときはどちらの場合もインジェクション対策をお忘れなく。

元レビューの補足(2016/09/06 13時ごろ追記)

元のMRコメントを書いたmorimorihogeです。
当該コメントは全体としては以下な感じでもうちょっと詳しく書いてました。

Screenshot 2016-09-06 13.23.05_2

↑スクショにもある通り、このコメントをした事例においては変数オブジェクトがStringであることが保証されていたので「直す必要はない」としたわけですね。
そこそこRubyを書いていると式展開なんかは当たり前のことではあるのですが、新人さんやRuby自体にまだ慣れていないメンバもいる中ではあえて常識と思っていることもしっかり解説するのが大事かな、と思って普段レビューしています。

元レビューも単に「式展開を使って下さい」でも良かったのですが、なぜ式展開を使うのか、どうして式展開がbetterなのかを知っておくことで今後思わぬ地雷を踏まなくなるといいなと思った次第です。Railsはビューの中でエラーが出るとデフォルトでは問答無用でアプリケーションエラーページが出てしまいますので、#try#digなど、多少想定外のデータが来ても良い書き方をするクセを付けておくのが良いかと思います。

関連記事

Rubyの式展開(string interpolation)についてまとめ: `#{}`、`%`、Railsの`?`


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。