In Ruby, what does the # frozen_string_literal: true magic comment change, and which strings in that file stay mutable?
answer
- per file, first comment section
- literals frozen and deduplicated
- FrozenError: can't modify frozen String
- interpolated literals exempt since 3.0
- method results stay mutable
basics
~10 sThe comment makes every plain string literal in that one file frozen and deduplicated, so mutating one raises FrozenError. Interpolated literals, strings returned by methods, +"" buffers and strings from other files stay mutable.
solid answer
~40 s`# frozen_string_literal: true` must sit in the file's first comment section, and it applies **only to that file**. Every plain string literal there is allocated once at parse time and frozen, so identical literals are the same object and `"abc".frozen?` is `true`; calling `<<`, `upcase!` or any other mutator on one raises `FrozenError` (`can't modify frozen String: "abc"`). What stays mutable: literals with **interpolation** such as `"id-#{n}"` (not frozen since Ruby 3.0), anything a method returns (`s + t`, `s.upcase`, `String.new`), a literal prefixed with unary plus (`+""`), and strings created in files without the comment. In Ruby 4.0 literals are still not frozen by default, so the comment remains the way to opt a file in.
code
ruby · 13 lines# frozen_string_literal: true
label = "total"
label.frozen? # => true
label.equal?("total") # => true
"order-#{rand(9)}".frozen? # => false (interpolated)
(label + "!").frozen? # => false (method result)
begin
label << "!"
rescue FrozenError => e
e.message # => "can't modify frozen String: \"total\""
endgo deeper
Recall where the comment goes and that mutating a literal under it raises FrozenError. Know that +"" gives you a mutable string.
Explain the scope precisely: one file, literals only, interpolated literals exempt since 3.0, and identical literals shared as one object.
Describe rolling the comment out across an old codebase: running the suite, fixing each FrozenError at the mutation site, and why the RuboCop autocorrection is unsafe.
Weigh enforcing the comment everywhere against waiting for a future default, given the chilled-string warnings Ruby 4.0 already offers.
## What the directive is A **magic comment** is a comment Ruby's parser reads as an instruction. `# frozen_string_literal: true` tells the parser that string literals in this file should be **allocated once at parse time and frozen**. It must appear in the first comment section of the file, before any code; a shebang line or an encoding comment may precede it. ```ruby # frozen_string_literal: true 3.times { p "hello".object_id } # same number three times "hello".frozen? # => true "hello".equal?("hello") # => true, one deduplicated object ``` Two effects follow: - **Frozen.** A frozen object rejects every mutation. `"hello" << "!"` raises `FrozenError` with the message `can't modify frozen String: "hello"`. `FrozenError` is a subclass of `RuntimeError`, and `FrozenError#receiver` returns the object that refused the change. - **Deduplicated.** Identical literals share one object, so a loop that evaluates `"ok"` a million times allocates nothing for it. ## Scope: one file, literals only The directive is **per file**. A library that uses it does not freeze literals in your code, and your comment does not affect the gems you load. It also applies only to **literals**, the text between quotes in the source. Everything else is untouched: | Expression in a file with the comment | Frozen? | |---|---| | `"total"` | yes | | `'total'`, `%q(total)` | yes | | `"order-#{id}"` (runtime interpolation) | no | | `"total" + "!"` | no, `+` returns a new string | | `"total".upcase` | no, a method result | | `+"total"` | no, unary plus returns an unfrozen copy | | `String.new("total")` | no | The interpolation row is a **version rule**: since Ruby 3.0, a literal with interpolation builds a fresh, unfrozen string each time it is evaluated, even under the comment. Before 3.0 it was frozen too. ## The values of the comment 1. `# frozen_string_literal: true` freezes and deduplicates the file's literals. 2. `# frozen_string_literal: false` explicitly keeps them mutable. In Ruby 4.0 this also means they are ordinary strings rather than chilled ones, so their mutation never warns. 3. No comment at all: in Ruby 4.0 the literals are **chilled**. They report `frozen?` as `false`, mutate normally, and warn only when deprecation warnings are enabled. Ruby 4.0 did not switch literals to frozen by default. ## Fixing the FrozenError it exposes Adding the comment to an old file often turns silent mutation into `FrozenError`. The usual repairs: - **A buffer you append to:** start it with `+""` (or `String.new`, remembering that `String.new` with no argument is binary-encoded) instead of `""`. - **A literal passed to a mutating method:** pass `+"text"`, or change the method to use the non-bang form. - **A constant someone mutates:** that mutation was already a bug that changed the constant for the whole process; build a new string instead. To locate the failing literal, read the backtrace line in the message and check the file that owns the literal, since the error is raised where the mutation happens, not where the string was written. ## Placement and spelling The parser only honours the directive in a narrow place: - It must be in the **first comment section** of the file. After the first code token it is ignored; with warnings on, Prism reports `'frozen_string_literal' is ignored after any tokens`. - It may follow a shebang line (`#!/usr/bin/env ruby`) and an encoding comment. - The value is `true` or `false`; the dashed spelling `frozen-string-literal` is accepted as well. - It affects the literals the parser reads from that file, including literals inside methods defined there that run much later, even when called from another file. A misplaced comment is a common reason a team believes a file is frozen when it is not; checking `"x".frozen?` in that file settles it. ## Why teams adopt it - **Fewer allocations** in hot paths that repeat the same literal. - **Safety:** a constant or default template cannot be edited by a stray `<<`. - **Tooling:** RuboCop's `Style/FrozenStringLiteralComment` cop is enabled by default and checks for the comment; its autocorrection is marked unsafe because adding the comment can introduce `FrozenError` at run time.
- Does the magic comment in your application file freeze the literals inside a gem it requires?No. The directive is read per source file by the parser, so it affects only the literals written in that file. Each gem file carries its own comment, or none.
- Why is RuboCop's autocorrection for Style/FrozenStringLiteralComment marked unsafe?Inserting the comment changes run-time behaviour: any code in that file that mutates a literal starts raising `FrozenError`. RuboCop cannot prove no literal is mutated, so the correction needs a human and a test run.
- Which exception class should a rescue name to catch a mutation of a frozen literal?`FrozenError`. It is a subclass of `RuntimeError`, so a bare `rescue` also catches it, but naming `FrozenError` is precise. `FrozenError#receiver` returns the frozen object that refused the change.
saying these in an interview costs you the question
- The magic comment freezes interpolated strings and method results as well
- The comment freezes literals in every file the program loads
- Two identical frozen literals are still two separate objects
- FrozenError is a TypeError, so rescue TypeError catches it
- Ruby 4.0 freezes all string literals, so the comment is redundant