Ruby

Perfect Frozen String Literal

現時点では解説がパーフェクトではないのを自覚しているが、めんどくさいので後でパーフェクトにする

Frozen String Literalとは

"str".freezeのようなリテラルのこと。

Feature#8992 (r43627) でこのようなリテラルを書くと事前にallocate & freezeされた文字列オブジェクト(内部的にはfstringと呼ばれる) が取得されるようになり、Ruby 2.1以降ではイミュータブルな文字列が必要な場面でこのリテラルを使うと高速になる。

以下、それに関連した機能や最適化がどのバージョンで入ったか、各バージョンでのお勧めの書き方をまとめておく。

Ruby 2.0.0まで

まだ入ってないので気にしなくてよい

Ruby 2.1〜2.2

  • Ruby 2.1: Use String#freeze and compiler tricks to replace "str"f suffix Feature#8992 (r43627)
    • "str".freezeでfstringが取得されるようになった。

frozen? #=> false 向けの書き方

普通に""と書くしかない。

frozen? #=> true 向けの書き方

"".freezeと書くと速くなる。これ以外の書き方はない。

Ruby 2.3〜2.4

  • Ruby 2.3: Immutable String literal in Ruby 3 Feature#11473 (r51659)
    • # frozen_string_literal: trueというコメントを最初に書くと文字列リテラルが全てfrozenな文字列を返すようになった。
  • Ruby 2.3: String#+@ and String#-@ Feature#11782 (r52917)
    • String#+@String#-@というメソッドが追加された。それぞれ単項演算子であり、+""-""のように使える。

参考:String#+@String#-@について

String#+@

  • self が freeze されている文字列の場合、元の文字列の複製を返します。 freeze されていない場合は self を返します。

String#-@

  • self が freeze されている文字列の場合、self を返します。 freeze されていない場合は元の文字列の freeze された複製を返します。

引用元:Ruby 2.4.0 リファレンスマニュアル > ライブラリ一覧 > 組み込みライブラリ > Stringクラス

frozen? #=> false 向けの書き方

  • # frozen_string_literal: trueを指定しているか、true/falseの両方に対応したい場合
    • +""が良い
      • +""str_uplusがStringに直接定義されているのに対し、String#dupはStringに対して直接定義されているわけではなく、rb_str_dup等が直接呼び出されるようにはなっていないため、+""の方が速い
        • 特に両方対応したい場合、+""frozen_string_literal: falseの時にrb_str_dupの処理が走らないので高速
      • String.newはリテラルではないのでスクリプトエンコーディングが効かず、エンコーディングがASCII-8bitになってしまうという問題がある。また、.newのメソッド呼び出し以外にStringという定数の取得が走るため、定数取得の際インラインキャッシュは使われるものの微妙に遅い
  • # frozen_string_literal: falseを指定しているか、pragma自体を書いてない場合
    • 普通に""と書けばよい

frozen? #=> true 向けの書き方

  • # frozen_string_literal: trueを指定している場合
    • ""と書けば良い
  • # frozen_string_literal: falseを指定しているか、pragma自体を書いてないか、true/falseの両方に対応したい場合
    • "".freeze と書くのが良い
      • -""とも書けるが、これはメソッド呼び出しが発生する上毎度別のオブジェクトが作られるため"".freezeより遅くなってしまう

Ruby 2.5以降

  • Ruby 2.5: apply opt_str_freeze to String#-@ ruby-core:80368 (r58144)
    • "".freezeと同様、-""でもメソッド呼び出しを経由しなくなり、また常に同じオブジェクトを返すようになったため高速になった

frozen? #=> false 向けの書き方

  • # frozen_string_literal: trueを指定しているか、true/falseの両方に対応したい場合
    • +""が良い
      • 理由はRuby 2.3〜2.4の時と同様
  • # frozen_string_literal: falseを指定しているか、pragma自体を書いてない場合
    • 普通に""と書けばよい

frozen? #=> true 向けの書き方

  • # frozen_string_literal: trueを指定している場合
    • ""と書けば良い
  • # frozen_string_literal: falseを指定しているか、pragma自体を書いてないか、true/falseの両方に対応したい場合
    • -"""".freezeのどちらかが良い
      • どちらも速度は変わらないはずだが、短い上に、true/falseの両方に対応したい場合に+""と対称性のある-""が個人的にはお勧め