🙌

Ruby 3.4 から frozen_string_literal をデフォルトで有効にする対応が開始される

2024/04/04に公開

注意:ここに書いてある情報は 2024/04/03 時点での開発版の ruby 3.4.0dev (2024-04-03T09:07:31Z master 25e28559c1) の情報になり、将来的に変更される可能性もあります。

関連チケット

なにがはじまるの?

タイトルの通りなんですが次の Ruby のリリースバージョンである Ruby 3.4 からマジックコメントの #frozen_string_literal: true をデフォルトで有効にする対応がはじまります。

# マジックコメントがない状態でも文字列リテラルがデフォルトで frozen された状態になる
pp "hoge".frozen?
# => true

# なので破壊的な変更をともなるメソッドを呼び出すとエラーになる
# error: can't modify frozen String: "hoge" (FrozenError)
"hoge".upcase!

と、言っても Ruby 3.4 でいきなり frozen_string_literal がデフォルトで有効になるわけではなくて以下のような移行パスで徐々に対応していく想定です。

  • Release R0: 非推奨警告を導入(非推奨警告が有効な場合のみ)
    • これが Ruby 3.4 で導入される
  • Release R1: 設定に関係なく非推奨警告を表示する
  • Release R2: frozen_string_literal をデフォルトで有効にする
  • NOTE: これらの移行パスが具体的にどういうバージョンで適用されるのかはまだ未定

なので Ruby 3.4 でいきなり非互換になり、既存のコードが壊れる可能性は(全く影響がないわけではないですが)低いです。

Ruby 3.4 でなにが変わるの?

Ruby 3.4 では新しく『チルド文字列(chilled strings)』という概念が追加されます。
と、言ってもユーザが意識すると言うよりかは内部で状態を持っているような形になります。
frozen_string_literal が設定されていない状態で Ruby のコードをコンパイルすると文字列リテラルで定義された文字列は『チルド文字列』として定義されます。

チルド文字列とは

チルド文字列は以下のような特性を持ちます。

  • frozen string として振る舞う
    • #frozen? を呼び出すと true を返す
  • 破壊的な変更を伴うメソッドを呼び出すと
    • 警告を出す
      • 非推奨警告(-W)が有効な場合のみ
    • チルド文字列としての特性を失う
  • その他、チルド文字列の場合
    • String#freeze : チルド文字列の特性を失い frozen string になる
    • String#-@ : mutable と同じ振る舞い
    • String#-@ : mutable と同じ振る舞い
    • String#clone : チルド文字列としてコピーされる

チルド文字列は frozen string として振る舞うので #frozen? は常に true を返します。

str = "homu"

# frozen string として振る舞うので #frozen? は true を返す
p str.frozen?   # => true

ただし、破壊的な変更を伴うメソッドを呼び出してもエラーにはなりません。

str = "homu"

# frozen string として振る舞うので #frozen? は true を返す
p str.frozen?   # => true

# ただし、これはエラーにはならない
p str.upcase!   # => "HOMU"

#frozen?true を返すのに #upcase! を呼び出してもエラーにならない事に注意する必要があります。
また、このときに -W を付けていると以下のような警告が出力されるようになります。

str = "homu"

# warning: 'String#upcase!': can't modify frozen String: "HOMU" (FrozenError)
p str.upcase!

更に破壊的な変更を伴うメソッドを呼び出すとチルド文字列としての特性を失うので以下のような挙動になります。

str = "homu"

# ここでチルド文字列としての特性を失う
# warning: 'String#upcase!': can't modify frozen String: "HOMU" (FrozenError)
p str.upcase!   # => "HOMU"

# なので frozen? も false を返すようになる
p str.frozen?   # => false

# 2回目は破壊的な変更を伴うメソッドを呼び出しても警告はでない
# no warning
p str.downcase!   # => "homu"

チルド文字列は String#clone だとチルド文字列としてコピーされます。

str = "hoge"

# チルド文字列としてコピーされる
str2 = str.clone

# 警告が出る
# warning: literal string will be frozen in the future
str2.upcase!

一方で String#dup だと通常の文字列としてコピーされます。

str = "hoge"

# 通常の文字列としてコピーされる
str2 = str.dup

# 警告はでない
str2.upcase!

チルド文字列として定義されないケース

このチルド文字列なのですが次のように式展開を伴う文字列に対しては有効にはなりません。

str = "homu#{42}"

# frozen されてない
p str.frozen?   # => false

# ここでも警告はでない
p str.upcase!   # => "HOMU"

これは #frozen_string_literal: true の場合でも frozen string にならない仕様と同じですね。

# frozen_string_literal: true

str = "homu#{42}"

# frozen されてない
p str.frozen?   # => false

# ここでも警告はでない
p str.upcase!   # => "HOMU42"

また文字列リテラルがチルド文字列として定義されるのはあくまでも『 #frozen_string_literal のマジックコメントがない場合』なのでマジックコメントがある場合はそちらの設定が優先されます。

# frozen_string_literal: true

puts RUBY_DESCRIPTION

# これは frozen string として定義される
str = "homu"

# frozen されている
p str.frozen?   # => true

# エラーになる
# error: can't modify frozen String: "homu" (FrozenError)
str.upcase!
# frozen_string_literal: false

# これは mutable な文字列として定義される
str = "homu"

# frozen されてない
p str.frozen?   # => false

# 警告もでない
str.upcase!

まとめ

  • Ruby 3.4 から frozen_string_literal がデフォルトで有効になる対応がはじまる
  • ただし Ruby 3.4 からいきなり有効になるのではなくてまずは移行するための下準備がはじまる
  • なので Ruby 3.4 から非互換になるわけではない
  • その一環として Ruby 3.4 では『チルド文字列』という概念が追加され影響があるコードに対して警告が出せるような対応がされる
  • 基本的には非互換になるわけではないが Ruby 3.4 時点で #frozen? がデフォルトで true になるようになるのでその部分で何かしらコードに影響がある可能性はある
GitHubで編集を提案

Discussion