FrozenError: can't modify frozen String の原因と直し方
freezeしたオブジェクトを破壊的メソッド(<<やupcase!など)で変更しようとすると、FrozenErrorになります。定数として扱いたい文字列にfreezeを付けると、以後は書き換えが禁止されます。
エラーメッセージの読み方
main.rb:2:in `<main>': can't modify frozen String: "Alice" (FrozenError)
main.rb- ファイル名
2- 行番号 — 実際にクラッシュした行
<main>- 発生場所 — <main> ならトップレベル、メソッド名ならそのメソッドの中
FrozenError- 例外クラス — 何が起きたか。ここを検索するのが最短です
can't modify frozen String: "Alice"- 詳細メッセージ — どの値が問題だったか
このエラーが出る典型パターン
パターン1
1 name = "Alice".freeze 2 puts(name << " Smith") ^
<<は文字列そのものを書き換える破壊的メソッドです。freezeした文字列には使えないため、新しい文字列を作る+に置き換える必要があります。
直し方: << を + にします。
パターン2
1 title = "Manager".freeze 2 puts(title << "!") ^
定数のように扱いたい文字列にfreezeを付けると、以後は破壊的メソッドによる変更が一律で禁止されます。
直し方: << を + にします。
パターン3
1 code = "AB".freeze 2 puts(code << "12") ^
freezeは変数への再代入ではなく、オブジェクトそのものを凍結します。中身を変える操作はすべて拒否されます。
直し方: << を + にします。
パターン4
1 tag = "ruby".freeze 2 puts(tag << "-lang") ^
+は元の文字列を変えずに新しい文字列を作るので、freezeされたオブジェクトにも安全に使えます。
直し方: << を + にします。
パターン5
1 prefix = "Mr.".freeze 2 puts(prefix << " Smith") ^
破壊的メソッド(!や<<が付くもの)を使う前に、レシーバがfreezeされていないか意識する必要があります。
直し方: << を + にします。
よくある誤解
「freezeは変数への再代入を禁止するだけのはず」という思い込みは誤りです。freezeはオブジェクトそのものを凍結するため、破壊的メソッドによる中身の変更も一律で禁止されます。
実務での勘所
ファイルの先頭に# frozen_string_literal: trueというマジックコメントを書くと、そのファイル内のすべての文字列リテラルが既定で凍結された状態になります。Ruby 2.3で導入されたこの仕組みは、同じ内容の文字列リテラルを毎回新しく作らずに使い回せるため実行速度とメモリ効率が上がるという利点があり、多くのスタイルガイドやRuboCopの既定ルールが全てのファイルの先頭にこの1行を書くことを推奨しています。このコメントを有効にしたファイルでは、既存の破壊的な文字列操作がFrozenErrorを起こす可能性がある点に注意が必要です。