String literals are chilled, not frozen¶
Level: 201 · anyone who has read "frozen string literals" in a Ruby changelog
One line: In Ruby 4.0 a string literal is still mutable by default and mutating it prints a deprecation warning only under -W:deprecated, while # frozen_string_literal: true or --enable=frozen-string-literal makes the same mutation a FrozenError; +"...", dup and String.new give strings you may change under every setting.
Ruby 2.3 added the magic comment # frozen_string_literal: true. Ruby 3.4 made every literal in a file without the comment chilled — still mutable, but flagged, so that mutating it emits a deprecation warning when those warnings are on (Feature #20205 ↗). Ruby 4.0 kept it there: literals are not frozen by default. The program below appends to a literal and runs itself five ways.
Verified output of literals_are_chilled_rb.rb — regenerated by tools/run_examples.py, never hand-typed.
$ ruby greet.rb
greeting.frozen? false
after << "hello, world"
$ ruby -W:deprecated greet.rb
greeting.frozen? false
after << "hello, world"
stderr: greet.rb:4: warning: literal string will be frozen in the future (run with --debug-frozen-string-literal for more information)
$ ruby --enable=frozen-string-literal greet.rb
greeting.frozen? true
FrozenError: can't modify frozen String: "hello"
$ ruby greet.rb # line 1: # frozen_string_literal: true
greeting.frozen? true
FrozenError: can't modify frozen String: "hello"
$ ruby -W:deprecated greet.rb # line 1: # frozen_string_literal: false
greeting.frozen? false
after << "hello, world"
Strings you are allowed to change, with no warning:
(+"hello").frozen? false
"hello".dup.frozen? false
String.new("hello").frozen? false
Strings that are shared on purpose:
(-"hello").equal?(-("hel" + "lo")) true
:hello.name.equal?(:hello.name) true
:hello.to_s.equal?(:hello.to_s) false
Reading the output¶
- By default, nothing is said.
frozen?isfalse, the append works, and there is no warning, becauseWarning[:deprecated]is off unless you turn it on. - Under
-W:deprecatedthe warning names the file and line, and the mutation still happens. The text promises the literal "will be frozen in the future", without naming a version. - The two ways to freeze, the magic comment and the command-line flag, make
frozen?trueand the append aFrozenError. # frozen_string_literal: falseopts a file out — no warning even under-W:deprecated.- Unfrozen copies:
+"hello"(which, since 3.4, returns a copy of a chilled literal instead of the literal itself),dup,String.new. - Shared on purpose:
-"hello"returns one frozen, deduplicated string, so two different expressions give the same object.Symbol#namereturns the same frozen string every time;Symbol#to_sreturns a new one, which Ruby 3.4 also started warning about when it is mutated.
What to do¶
- Run the test suite with
-W:deprecated, or setWarning[:deprecated] = truein the test helper, to find every mutation of a literal now rather than on the release that freezes them. - In code you control, put
# frozen_string_literal: trueon line 1, and build strings from+""orString.newwhen you mean to append.
If you are coming from another language¶
Python and Java strings are immutable, literal or not; a Rust string literal is a &'static str, a borrow of bytes in the binary — the Rust library's &'static str ↗. Perl strings are mutable values with no notion of a frozen literal. (Not machine-checked here.)