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

Ruby: 文字列リテラル同士はスペース文字で結合される

Slackの以下の書き込みで、今まで気づかなかったRubyの文字列リテラルの挙動を知りました。

Rubyの文字列リテラルの挙動

まずは調べてみました。使ったのはRuby 3.0.0-preview2です。

結合される場合

# 二重引用符の文字列リテラル、スペース区切り
"foo" "bar"
#=> "foobar"
# 一重引用符の文字列リテラル、スペース区切り
'foo' 'bar'
#=> "foobar"
# 引用符をミックス、スペース区切り
"foo" 'bar'
#=> "foobar"

'foo' "bar"
#=> "foobar"
# 引用符の間にスペースなし
"foo""bar"
#=> "foobar"

'foo''bar'
# => "foobar"
# バックスラッシュと改行での区切り
"foo"\
"bar"
#=> "foobar"

結合できない場合

だいたい予想は付きますが、やってみました。

数値リテラルと文字列リテラルのスペース区切りはエラーになります。

1 "foo" 
#=> syntax error, unexpected string literal, expecting end-of-input

シンボルと文字列も同じくエラーになります。

:foo "bar"
#=> syntax error, unexpected string literal, expecting end-of-input

シンボルを文字列に変換してみてもエラーです。以下で変換された文字列は文字列リテラルではないので、文字列リテラルとは扱いが異なると考えればよいと思います。

:foo.to_s :bar.to_s
#=> wrong number of arguments (given 1, expected 0) (ArgumentError)

おまけ: スペース文字以外の場合

以下は、試しにスペース文字(U+0020)以外のホワイトスペース文字をいくつか区切りに使ってみた結果だけを書きます。

タブ文字(U+00A0
成功
nbsp(U+00A0
成功
en space(U+2002
エラー(syntax error, unexpected local variable or method, expecting end-of-input)
em space(U+2003
エラー(syntax error, unexpected local variable or method, expecting end-of-input)

参考: スペース - Wikipedia

仕様を探してみた

以下のruby/specを探してみました。

ruby/spec - GitHub

# https://github.com/ruby/spec/blob/master/language/string_spec.rb#L225
describe "Ruby String literals" do
  def str_concat
    "foo" "bar" "baz"
  end

  def long_string_literals
    "Beautiful is better than ugly." \
    "Explicit is better than implicit."
  end

  it "on a single line with spaces in between are concatenated together" do
    str_concat.should == "foobarbaz"
  end

  it "on multiple lines with newlines and backslash in between are concatenated together" do
    long_string_literals.should == "Beautiful is better than ugly.Explicit is better than implicit."
  end
  # 略

文字列リテラルのスペース文字や改行による結合はspecに含まれていますね。

また、公式ドキュメントの「文字列リテラル」にも記載されています。

参考: リテラル (Ruby 2.7.0 リファレンスマニュアル)

空白を間に挟んだ文字列リテラルは、コンパイル時に1つの文字列リテラルと見倣されます。
docs.ruby-lang.orgより

RuboCopには怒られる

そもそも文字列リテラルを+なしで結合しようとすると、素のRuboCopで怒られが発生します。冒頭のコードでやってみました。

# frozen_string_literal: true

a = { 'a' => 1, 'b' => 2, 'c' => 3 }.slice('a', 'b' 'c')
p a

上をtest.rbに保存し、RuboCopをインストールしてチェックします。

$ gem install rubocop
# (略)
Successfully installed parallel-1.20.1
Successfully installed ast-2.4.1
Successfully installed parser-2.7.2.0
Successfully installed rainbow-3.0.0
Successfully installed regexp_parser-2.0.0
Successfully installed rubocop-ast-1.3.0
Successfully installed ruby-progressbar-1.10.1
Successfully installed unicode-display_width-1.7.0
Successfully installed rubocop-1.6.1
$ rubocop test.rb
warning: parser/current is loading parser/ruby30, which recognizes
warning: 3.0.0-dev-compliant syntax, but you are running 3.0.0.
warning: please see https://github.com/whitequark/parser#compatibility-with-ruby-mri.
Inspecting 1 file
W

Offenses:

test.rb:3:49: W: Lint/ImplicitStringConcatenation: Combine 'b' and 'c' into a single string literal, rather than using implicit string concatenation. Or, if they were intended to be separate method arguments, separate them with a comma.
a = { 'a' => 1, 'b' => 2, 'c' => 3 }.slice('a', 'b' 'c')
                                                ^^^^^^^

というわけで、この+なしの文字列リテラル結合を業務用のコードで積極的に使うことはなさそうです。

まとめ

  • Rubyの2つ以上の文字列リテラルは、スペース文字を挟んで配置されると1つの文字列リテラルとして扱われる
  • 文字列リテラルの囲みが一重引用符と二重引用符かどうかでこの挙動は変わらない
  • スペース文字以外に改行やタブ文字で区切った場合や、区切り文字を挟まない場合も同様
  • この挙動は文字列リテラル同士が隣り合っている場合に発生する

関連記事

速報: Ruby 3.0.0がリリースされました

Rubyの内部文字コードはUTF-8ではない…だと…?!


CONTACT

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