Javier Valencia Javier Valencia
Gema facetada en rojo rubí junto al número de versión 3.4, portada de la serie de novedades de Ruby

Novedades de Ruby 3.4: it, Prism por defecto y el GC modular

Javier Valencia · · 5 min de lectura · 5 visitas · Desarrollo
ruby desarrollo programacion series tutorial

Quinta entrega de la serie Novedades de Ruby. Tiempo de lectura estimado: 6 minutos.

Ruby 3.4 salió, como manda la tradición, el 25 de diciembre de 2024. Escribo esto en agosto de 2026, con veinte meses de perspectiva y varias aplicaciones Rails corriendo sobre ella en producción, así que puedo decirlo con tranquilidad: fue la actualización menos dramática de toda la serie 3.x. Y eso, que suena a poco, es exactamente lo que la convierte en una gran versión. 3.4 no vino a deslumbrar; vino a consolidar. Cambió el parser, empezó a descongelar (o más bien a congelar) un debate de una década sobre los string literals, y abrió la puerta del recolector de basura a implementaciones externas. Trabajo de fontanería del bueno.

it: el parámetro que llevaba diez años atascado

Empiezo por lo único que se ve a simple vista. Desde 2.7 teníamos los parámetros numerados (_1, _2), que siempre me parecieron funcionales pero feos, como de lenguaje de calculadora. Lo que la comunidad pedía era it, y lo que lo frenaba era un miedo muy concreto: medio mundo Ruby escribe tests con RSpec, donde it es un método que se usa millones de veces al día. En 3.4 por fin se resolvió el nudo:

["foo", "bar", "baz"].map { it.upcase }
#=> ["FOO", "BAR", "BAZ"]

it es una soft keyword: solo actúa como parámetro implícito dentro de un bloque sin parámetros nombrados. Los detalles finos importan:

  • Si existe una variable local llamada it, gana la variable. Por eso RSpec sigue funcionando sin inmutarse.
  • Al contrario que _1, funciona en bloques anidados: cada nivel tiene su propio it.
  • Mezclar it y _1 en el mismo bloque es un error de sintaxis. Bien pensado: o uno, o el otro.

Mi veredicto tras casi dos años: en one-liners de transformación (map, select, sum) lee de maravilla y _1 ha desaparecido de nuestro código nuevo. En bloques de más de una línea seguimos nombrando el parámetro, y esa regla de estilo nos ha ahorrado discusiones.

Prism: el nuevo parser que no ves

El cambio más profundo de 3.4 es de los que no aparecen en ninguna demo: Prism sustituyó a parse.y como parser por defecto de Ruby. Para el usuario final apenas hay diferencia visible (el parser antiguo sigue disponible con --parser=parse.y), pero para el ecosistema es un antes y un después.

Durante años, cada herramienta que necesitaba entender código Ruby —RuboCop, los language servers, Sorbet, los formateadores— mantenía o consumía su propio parser, con sus propias discrepancias respecto al de verdad. Prism nace como biblioteca reutilizable, portable y con recuperación de errores decente, pensada tanto para CRuby como para JRuby, TruffleRuby o cualquier editor. En el día a día se nota en cosas pequeñas: mensajes de error de sintaxis más claros y localizados, y un tooling que por fin habla el mismo idioma que el intérprete. Es el tipo de inversión en infraestructura que solo se aprecia con los años.

Chilled strings: la congelación más lenta de la historia

El comentario mágico # frozen_string_literal: true lleva con nosotros desde Ruby 2.3, allá por 2015, como paso intermedio hacia un futuro en que todos los string literals fueran inmutables por defecto. Una década después, 3.4 dio el siguiente pasito con los llamados chilled strings: en los ficheros sin comentario mágico, los literales siguen siendo mutables, pero mutar uno emite un aviso de deprecación… que además está desactivado por defecto. Hay que pedirlo:

# Con: ruby -W:deprecated app.rb
# (o Warning[:deprecated] = true)
buf = ""
buf << "hola"
# warning: literal string will be frozen in the future

¿Escapatorias? Varias: +"cadena" devuelve una copia mutable, String.new siempre lo fue, y el flag --disable-frozen-string-literal apaga todo el mecanismo. Es la migración más cautelosa que he visto en un lenguaje: aviso opcional primero, aviso por defecto después, y solo al final el cambio real. En nuestras apps activamos el warning en CI, afloraron un par de gems antiguas mutando literales, y santas pascuas. Y como adelanto del próximo post: Ruby 4.0 llegó un año después y tampoco dio el paso final. La congelación continúa, a su ritmo glacial. Nunca mejor dicho.

El recolector de basura se vuelve modular

La otra obra de fontanería mayor: 3.4 introdujo una capa de abstracción sobre el GC que permite cargar implementaciones alternativas como bibliotecas dinámicas. Compilando Ruby con --with-modular-gc puedes elegir el recolector en arranque con la variable de entorno RUBY_GC_LIBRARY, y como prueba de concepto se publicó un GC experimental basado en MMTk, el toolkit de memoria escrito en Rust que usan otros lenguajes para investigar recolectores.

¿Le cambió la vida a alguien en producción? A mí no, de momento. Pero estratégicamente es enorme: el GC de CRuby siempre ha sido monolítico, y poder experimentar con recolectores sin hacer un fork del intérprete es la manera de que dentro de unos años tengamos opciones serias donde hoy solo hay una.

YJIT afina (y el resto también)

YJIT siguió su progresión de cada año, esta vez con mejoras poco vistosas y muy efectivas: un límite de memoria unificado con --yjit-mem-size (128 MiB por defecto, mucho más fácil de razonar que los flags anteriores), asignación de registros para variables locales, inlining de métodos triviales y varios métodos de Array (each, select, map) reescritos en Ruby precisamente para que el JIT pueda optimizarlos mejor. Ese último punto me sigue pareciendo elegantísimo: el intérprete confía tanto en su compilador que reescribe sus primitivas C en Ruby.

Fuera del JIT hubo más regalos discretos: la gem json alrededor de 1,5 veces más rápida, y Happy Eyeballs v2 activado por defecto en sockets, que resuelve IPv6 e IPv4 en paralelo y elimina esos timeouts fantasma en redes con IPv6 a medio configurar. En un monolito Rails que habla con veinte servicios, esto último se nota más de lo que parece.

Retrospectiva

Ruby 3.4 fue la versión en la que el equipo dejó de correr y ordenó la casa: parser nuevo, GC extensible, una transición de una década avanzando con pies de plomo y un JIT que ya nadie discute. La subida desde 3.3 fue de las de cambiar el número en el Dockerfile y a otra cosa. Si quieres el detalle exhaustivo, la nota de lanzamiento oficial y el repaso de rubychanges son las referencias que he usado aquí.

Lo que nadie sabía en aquella Navidad de 2024 es que esta versión tranquila era la calma antes del salto: la siguiente no se llamaría 3.5. En la próxima y última entrega de la serie: Ruby 4.0, el porqué del número, Ruby::Box y ZJIT. El resto de la serie, como siempre, bajo la etiqueta ruby.