# Ruby 4.0: el salto de versión, Ruby::Box y ZJIT

*Sexta y última entrega de la serie **[Novedades de Ruby](/search?tag=ruby)**. Tiempo de lectura estimado: 7 minutos.*

[El 25 de diciembre de 2025](https://www.ruby-lang.org/en/news/2025/12/25/ruby-4-0-0-released/) no salió Ruby 3.5. Salió **Ruby 4.0**. El número coincidió con una fecha redonda: treinta años desde aquel Ruby 0.95 de diciembre de 1995, y el equipo decidió que el aniversario, junto con las dos grandes novedades del ciclo, merecía saltar de versión mayor. Escribo esto en agosto de 2026, con ocho meses de 4.0 rodando, y este post cierra la serie: toca contar qué trae, qué promete y qué arco hemos recorrido desde el primer post.

## Por qué 4.0 (y por qué no se rompió casi nada)

Lo primero que pregunta todo el mundo ante una versión mayor: ¿qué se rompe? Y la respuesta honesta es: **muy poco**. Matz mantiene desde 3.0 la filosofía de que el número mayor marca una época, no una masacre de compatibilidad. Aun así, hay limpieza que conviene conocer antes de actualizar un Rails:

- **`Ractor.yield` y `Ractor#take` desaparecen**, sustituidos por una API nueva (ahora vamos a ella).
- La biblioteca **CGI se va de la stdlib** (solo sobrevive `cgi/escape`).
- Otra tanda de *default gems* pasa a *bundled gems*: `logger`, `irb`, `ostruct`, `benchmark`, `rdoc`, `fiddle`… Si las usas, al `Gemfile`.
- **`Set` se reimplementa en el core** (más rápido, `inspect` ahora imprime `Set[1, 2, 3]`), y `Pathname` pasa a estar disponible sin `require` para lo básico.
- Detalle sintáctico que me gusta: las líneas pueden **empezar por `&&`, `||`, `and` y `or`**, igual que llevamos años encadenando puntos.

En la práctica, nuestra actualización fue añadir tres gems al `Gemfile` y revisar un par de usos de `Set`. Para ser una versión mayor, un paseo. Lo importante de 4.0 no está en lo que rompe sino en lo que estrena: dos features experimentales con ambición de década.

## Ruby::Box: monkey patches en cuarentena

**`Ruby::Box`** es lo más conceptualmente nuevo que le ha pasado al modelo de objetos de Ruby en mucho tiempo: **espacios de nombres aislados dentro del mismo proceso**. Un box carga código en su propio universo: sus definiciones de clases, sus variables globales y —esto es lo gordo— sus monkey patches no se escapan al resto del programa. Es experimental y hay que activarlo con la variable de entorno `RUBY_BOX=1`:

```ruby
box = Ruby::Box.new
box.require("csv")

box::CSV.parse("a,b")  # existe dentro del box
CSV                    # NameError: el namespace principal, limpio
```

Las clases del core son las mismas a ambos lados de la frontera, pero los parches que un box les aplique se quedan dentro. Los casos de uso que plantea el anuncio oficial dan para soñar: testear código que parchea clases sin contaminar el resto de la suite, **cargar dos versiones de la misma gem en un proceso**, o incluso ejecutar dos versiones de una aplicación en paralelo para hacer *blue-green* dentro del mismo proceso.

Después de veinte años sufriendo gems que redefinen `String#to_json` a traición, entiendo perfectamente por qué existe. Ahora, seamos claros con el estado real: `gem "nombre"` aún no funciona dentro de un box, y la propia documentación avisa de que activar `RUBY_BOX=1` puede romper código **aunque no uses ningún box**. En producción, ni de broma todavía. Como dirección, es de lo más prometedor que ha señalado Ruby en años.

## ZJIT: el JIT «de libro de texto»

La segunda estrella es **ZJIT**, el nuevo compilador JIT desarrollado por el mismo equipo de Shopify que hizo YJIT, y presentado oficialmente como su siguiente generación. El giro es arquitectónico: donde YJIT compila *basic blocks* de forma perezosa (el famoso *lazy basic block versioning*), ZJIT es un **compilador por métodos clásico, con representación intermedia en SSA**: la arquitectura «de libro de texto» que usan los JIT de otros lenguajes, pensada para poder aplicar optimizaciones tradicionales y para que más gente pueda contribuir.

El estado a día de hoy, dicho por el propio anuncio sin maquillaje: **más rápido que el intérprete, todavía más lento que YJIT**, y la recomendación oficial es experimentar con él (`--zjit`) pero no desplegarlo en producción. Compilar Ruby con soporte ZJIT requiere Rust 1.85 o superior. Yo lo he probado en un worker de staging por pura curiosidad: funciona, no se cae, y no es su momento aún. Lo interesante no es el presente sino la apuesta: YJIT ha ido tocando techo en su arquitectura, y ZJIT es la plataforma sobre la que construir los próximos diez años de rendimiento. Los detalles técnicos están en la [documentación oficial de ZJIT](https://docs.ruby-lang.org/en/4.0/jit/zjit_md.html).

## Ractor por fin tiene una API decente

Los Ractors llevan desde 3.0 en esta serie con la coletilla de «prometedores pero verdes». En 4.0 la API vieja de `yield`/`take` muere y llega **`Ractor::Port`**, un mecanismo de comunicación mucho más claro: cualquier ractor puede escribir en un puerto, solo su creador puede leer de él.

```ruby
port = Ractor::Port.new

Ractor.new(port) { |p| p << expensive_work }
result = port.receive
```

Se suman `Ractor#join` y `#value` (misma semántica que en `Thread`), y `Ractor.shareable_proc` para compartir bloques entre ractors con garantías. Por debajo, además, hubo trabajo serio de rendimiento: estructuras *lock-free* para strings congelados y menos contención en la caché de métodos. Sigo sin ejecutar Ractors en producción —el ecosistema de gems no acompaña todavía—, pero por primera vez desde 2020 la API se parece a algo que usaría voluntariamente.

## Lo que viene

Con las dos features estrella en fase experimental, el interés de 2026 está en la maduración. **Box** necesita que el modo deje de ser opt-in arriesgado y que Bundler aprenda a vivir dentro de un box; **ZJIT** necesita alcanzar a YJIT en los benchmarks antes de plantearse relevarlo. Ninguna de las dos cosas parece lejana viendo el ritmo de commits.

Y el ecosistema ya está respondiendo al salto de versión: [JRuby 10.1](https://www.jruby.org/2026/04/21/jruby-10-1-0-0.html) salió en abril apuntando a compatibilidad con Ruby 4.0 (y de regalo redujo el tamaño base de todos sus objetos de 32 a 24 bytes), y [mruby 4.0.0](https://mruby.org/) llegó ese mismo mes para el mundo embebido. Cuando las implementaciones alternativas se alinean tan rápido, es señal de que el 4.0 va en serio.

## Cierre de la serie

Empezamos esta serie con Ruby 3.0 y la promesa del **3x3**: un Ruby tres veces más rápido que el 2.0. Seis versiones después, el balance del arco completo me parece este: la promesa de rendimiento **se cumplió por un camino inesperado** (no fue MJIT sino YJIT quien la hizo realidad en aplicaciones de verdad); la concurrencia con Ractors ha tardado cinco años en tener una API digna y aún no ha despegado; el tipado con RBS sigue siendo más discreto de lo que se auguraba; y mientras tanto, casi sin anunciarlo, Ruby se ha ido volviendo más serio por dentro: Prism, GC modular, chilled strings, y ahora Box y ZJIT.

Treinta años después, el lenguaje con el que me gano la vida sigue optimizando para la felicidad del programador, pero ya no pide perdón por su rendimiento. Como despedida no está mal. La serie completa queda recogida bajo la etiqueta [ruby](/search?tag=ruby), y las referencias de este cierre son la [nota de lanzamiento oficial de Ruby 4.0](https://www.ruby-lang.org/en/news/2025/12/25/ruby-4-0-0-released/) y el [repaso detallado de rubychanges](https://rubyreferences.github.io/rubychanges/4.0.html). Gracias por acompañarme versión a versión. Nos leemos en la 4.1, aunque sea fuera de serie.
