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: true for 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) and FL_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.