Ruby 3.4 frozen string literals: What Rails developers need to know
Ruby string mutability & what’s changing
- Ruby strings remain mutable by default; the change targets only string literals (e.g.,
"foo"in source). - Literals will be created as frozen/immutable by default; you can still create mutable strings via
+"foo",String.new, concatenation, etc. - This behavior has long been opt‑in per file via
# frozen_string_literal: true; the plan is to flip the default in a future major version.
Motivation: performance, GC, and correctness
- Freezing literals avoids reallocating the same string each time a method or loop runs, reducing allocations and GC pressure.
- Frozen literals can be safely reused and interned/deduplicated, improving memory usage.
- Benchmarks shared in the thread show modest but real speedups.
- Several people note that enabling frozen literals surfaced subtle bugs where code unintentionally mutated shared strings.
Migration plan & comparison to Python 2→3
- Many argue this is nothing like the Python 2→3 transition:
- The feature has existed since Ruby 2.3 (almost a decade).
- Linters like RuboCop have pushed
# frozen_string_literal: truefor years. - Ruby 3.4 adds opt‑in warnings; future versions will add opt‑out warnings before changing the default.
- There’s an escape hatch (
RUBYOPT="--disable-frozen-string-literal") expected to exist long‑term.
- Skeptics worry about ecosystem-wide churn and dependency lag, but others think most gems are already compatible.
How it works under the hood
- At parse time, string literals are turned into frozen “fstrings” stored in an intern table.
- Strings carry flags like
FL_FREEZE(frozen) andFL_FSTR(interned); mutation checks these flags. - The intern table uses weak references; Ruby’s mark-and-sweep GC cleans up unused interned strings without reference counting.
Language design & comparisons
- Several comments situate Ruby among languages with immutable literals (C, Python, JS, Java, Rust, OCaml) versus older dynamic languages with mutable strings (Perl, Smalltalk, Common Lisp).
- There’s debate over whether mutable-by-default is simpler or more of a footgun; some see this change as overdue, others as a micro-optimization that complicates string handling.