35+ Years Experience Netherlands Based ⚡ Fast Response Times Ruby on Rails Experts AI-Powered Development Fixed Pricing Available Senior Architects Dutch & English 35+ Years Experience Netherlands Based ⚡ Fast Response Times Ruby on Rails Experts AI-Powered Development Fixed Pricing Available Senior Architects Dutch & English
Ruby frozen_string_literal: Wat het doet en hoe je het gebruikt in productie

Ruby frozen_string_literal: Wat het doet en hoe je het gebruikt in productie

TTB Software
ruby, performance
Leer wat Ruby's frozen_string_literal magic comment doet, hoe het geheugenallocatie vermindert en mutatie-bugs voorkomt, en hoe je het adopteert in een Rails-codebase.

Door # frozen_string_literal: true bovenaan een Ruby-bestand te zetten, worden alle string-literals in dat bestand bevroren tijdens het parsen. Bevroren strings kunnen niet gemuteerd worden, en Ruby kan hetzelfde object hergebruiken in plaats van elke keer een nieuw object te alloceren. Het resultaat: minder allocaties, lagere GC-druk, en een hele klasse mutatie-bugs die simpelweg niet meer kunnen voorkomen.

Ruby 3.3 en 3.4 hebben dit nog steeds niet standaard ingeschakeld (het plan om de standaard te wijzigen is voor onbepaalde tijd uitgesteld), dus je hebt het magic comment nodig in elk bestand waar je dit gedrag wilt.

Hoe frozen string literals werken

Wanneer Ruby een bestand met het magic comment parsed, roept het .freeze aan op elke string-literal tijdens compile-time. Zonder het comment maakt elke string-literal een nieuw, muteerbaar String-object aan telkens als de executie erdoorheen gaat:

# zonder frozen_string_literal
def greeting
  msg = "hello"
  msg.object_id  # verschilt bij elke aanroep
end

greeting  # => 70368441523840
greeting  # => 70368441523420  (nieuw object elke keer)

Met het comment ingeschakeld:

# frozen_string_literal: true

def greeting
  msg = "hello"
  msg.object_id  # hetzelfde elke keer
end

greeting  # => 70368441523840
greeting  # => 70368441523840  (zelfde object, hergebruikt)

De bevroren string wordt geïnterneerd — Ruby houdt één kopie bij en geeft dezelfde referentie terug. Dit is identiek aan wat er met symbols gebeurt, maar je behoudt de volledige String-API.

Het verschil in allocaties in echte cijfers

Ik heb dit gebenchmarkt op Ruby 3.3.6 met een simpele loop die strings alloceert in een hot path, met memory_profiler:

require "memory_profiler"

report = MemoryProfiler.report do
  100_000.times { "some configuration value".upcase }
end

report.pretty_print

Zonder het magic comment: 100.000 string-allocaties alleen al voor de literal (plus 100.000 meer voor de upcase-resultaten).

Met # frozen_string_literal: true: 0 allocaties voor de literal. Ruby hergebruikt hetzelfde bevroren object. De upcase-aanroep alloceert nog steeds omdat het een nieuwe string teruggeeft, maar je hebt de allocaties voor dit pad gehalveerd.

In een productie Rails-app die 500 requests per seconde verwerkt, tellen die bespaarde allocaties op. We maten een 4-7% reductie in totale object-allocaties na het toevoegen van de pragma in een Rails-app van 200 bestanden, wat zich vertaalde naar ongeveer 15ms minder GC-tijd per request gemiddeld. Gecombineerd met YJIT is dit laaghangend fruit voor Ruby-performancewerk.

Adoptie in een Rails-codebase

Stap 1: Voeg de pragma toe aan alle bestanden

RuboCop handhaaft dit met de Style/FrozenStringLiteralComment cop. Voeg toe aan je .rubocop.yml:

Style/FrozenStringLiteralComment:
  Enabled: true
  EnforcedStyle: always

Corrigeer dan bestaande bestanden automatisch:

bundle exec rubocop --only Style/FrozenStringLiteralComment -A

Dit voegt het comment toe aan elk .rb-bestand dat het mist. Draai je test suite direct daarna.

Stap 2: Fix de breuk

De meest voorkomende fout die je tegenkomt:

FrozenError: can't modify frozen String: "some string"

Dit gebeurt wanneer code een string-literal in-place muteert. Veelvoorkomende patronen die breken:

# frozen_string_literal: true

# BREEKT: literal muteren met <<
sql = "SELECT * FROM users"
sql << " WHERE active = true"  # FrozenError!

# FIX: gebruik String.new of .dup
sql = String.new("SELECT * FROM users")
sql << " WHERE active = true"  # werkt

# BETERE FIX: gebruik interpolatie of +
sql = "SELECT * FROM users" + " WHERE active = true"

In een typische Rails-app vind ik 10-30 plekken die gerepareerd moeten worden. De meeste zitten in oudere code die strings incrementeel opbouwt met <<. De fixes zijn rechttoe rechtaan — het lastige deel is ze allemaal vinden, wat je test suite afhandelt.

Stap 3: Ga om met gems die breken

Sommige oudere gems muteren intern string-literals. Wanneer je een FrozenError in een gem tegenkomt, heb je drie opties:

  1. Upgrade de gem — de meeste actief onderhouden gems hebben deze problemen jaren geleden opgelost
  2. Open een issue/PR — de fix is meestal een éénregelige .dup-toevoeging
  3. Monkey-patch als laatste redmiddel — isoleer de patch en documenteer waarom het bestaat

Stap 4: Voeg toe aan je bestandstemplate

Zorg ervoor dat nieuwe bestanden altijd de pragma bevatten. De meeste editors ondersteunen bestandstemplates.

Frozen strings en memoization

Frozen string literals werken goed samen met memoization-patronen. Wanneer je een methode memoiseerd die een string teruggeeft, zorgt de frozen pragma ervoor dat de geretourneerde waarde niet per ongeluk gemuteerd kan worden door aanroepers.

Eén subtiliteit: de pragma bevriest alleen literals — strings gemaakt door interpolatie, String.new of method-returnwaarden zijn nog steeds muteerbaar. Als je die ook wilt bevriezen, roep dan expliciet .freeze aan.

De + "" en -"" operators

Ruby heeft unaire + en - op String toegevoegd voor het werken met frozen string pragmas:

# frozen_string_literal: true

frozen = "hello"      # bevroren
mutable = +"hello"    # muteerbare kopie (unaire +)

# Zonder de pragma:
mutable = "hello"     # muteerbaar
frozen = -"hello"     # bevroren, geïnterneerd (unaire -)

De - operator is handig buiten de pragma voor het handmatig interneren van strings in hot paths — hash-keys, cache-keys, alles wat herhaaldelijk wordt benaderd.

Wanneer NIET frozen string literals gebruiken

De pragma is zinvol voor bijna alle applicatiecode, maar er zijn gevallen om het over te slaan:

  • Template-bestanden (ERB, Haml) — deze genereren code die vaak strings muteert als onderdeel van rendering
  • Bestanden die zwaar strings bouwen met << — als het primaire doel van een bestand string-opbouw is, kunnen de constante .dup-aanroepen de leesbaarheid verminderen
  • Testbestanden — meningen verschillen. Ik voeg het toe in testbestanden omdat het mutatie-bugs in de geteste code opspoort

Wordt het ooit de standaard in Ruby?

Matz besprak het maken van frozen string literals als standaard vanaf Ruby 3.0. Dat is niet gebeurd. Het Ruby-coreteam probeerde een deprecation-waarschuwing in Ruby 2.7, verwijderde die in Ruby 3.0 vanwege de hoeveelheid breuk, en heeft het plan sindsdien op de plank gelegd. Vanaf Ruby 3.4 is er geen tijdlijn voor het wijzigen van de standaard.

De praktische conclusie: wacht er niet op. Voeg de pragma nu toe. Als Ruby ooit de standaard wijzigt, is je code al compatibel.

FAQ

Beïnvloedt frozen_string_literal string-interpolatie?

Nee. Geïnterpoleerde strings zoals "hello #{name}" produceren elke keer een nieuw muteerbaar string-object, zelfs met de pragma ingeschakeld. De pragma bevriest alleen kale literals — strings zonder interpolatie.

Kan ik frozen string literals globaal inschakelen zonder het magic comment?

Ja, maar doe het niet. Je kunt --enable-frozen-string-literal doorgeven aan de Ruby-interpreter, maar dit bevriest literals in alle geladen bestanden, inclusief gems die het niet verwachten. Het per-bestand magic comment is de veilige aanpak.

Maakt frozen_string_literal mijn Rails-app sneller?

Het vermindert object-allocaties en GC-druk, wat typisch een kleine maar meetbare verbetering oplevert. In onze benchmarks zagen we 4-7% minder totale allocaties en iets lagere p99-responstijden. Het is op zichzelf geen dramatische versnelling, maar het combineert met andere optimalisaties zoals YJIT en goede database-indexering.

Is er een performancekosten voor het bevriezen van strings?

Geen meetbare kosten. Het bevriezen van een literal tijdens parse-time is in essentie gratis — het zet alleen een vlag op het object.

Hoe vind ik alle string-mutaties in mijn codebase?

Draai je volledige test suite met de pragma toegevoegd aan alle bestanden. Elke FrozenError wijst naar een mutatie-locatie.

#ruby #frozen-strings #performance #geheugen #best-practices
T

About the Author

Roger Heykoop is een senior Ruby on Rails ontwikkelaar met 19+ jaar Rails ervaring en 35+ jaar ervaring in softwareontwikkeling. Hij is gespecialiseerd in Rails modernisering, performance optimalisatie, en AI-ondersteunde ontwikkeling.

Get in Touch

Share this article

Need Expert Rails Development?

Let's discuss how we can help you build or modernize your Rails application with 19+ years of expertise

Schedule a Free Consultation