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

こんにちは、hachi8833です。

rubystrconcat

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

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

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

str2 = "おいしい"

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

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

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といえどもさすがにエラーになります(使う人はいないと思いますが)。

詳しくはObjectクラスBasicObjectをご覧ください。

理由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 on RailsによるWEBシステム開発、Android/iPhoneアプリ開発、電子書籍配信のことならお任せください この記事を書いた人と働こう! Ruby on Rails の開発なら実績豊富なBPS

この記事の著者

hachi8833

Twitter: @hachi8833、GitHub: @hachi8833

コボラー、ITコンサル、ローカライズ業界、Rails開発を経てTechRachoの編集・記事作成を担当。
これまでにRuby on Rails チュートリアル第2版の半分ほど、Railsガイドの初期翻訳ではほぼすべてを翻訳。その後も折に触れてそれぞれ一部を翻訳。
かと思うと、正規表現の粋を尽くした日本語エラーチェックサービス enno.jpを運営。
実は最近Go言語が好き。
仕事に関係ないすっとこブログ「あけてくれ」は2000年頃から多少の中断をはさんで継続、現在はnote.muに移転。

hachi8833の書いた記事

BPSアドベントカレンダー

週刊Railsウォッチ

インフラ

BigBinary記事より

ActiveSupport探訪シリーズ