Ruby 4.0 Just Dropped. Here's What Actually Matters.
Ruby 4.0 landed on Christmas Day. Right on schedule.
While tech Twitter debates whether Ruby is "dead" for the thousandth time, Shopify's compiler team just shipped a next-generation JIT. Matz chose 4.0 to celebrate Ruby's 30th birthday. And the release notes run deeper than most people realize.
Let me break down what actually matters for production Ruby apps.
ZJIT: The Next Generation Compiler
Here's the big one. ZJIT is a brand new JIT compiler from the same Shopify team that built YJIT.
Why build another JIT when YJIT already delivers 30% speedups in production? Because YJIT hit a ceiling. It compiles bytecode directly to machine code, one basic block at a time. Fast to build. Hard to optimize further.
ZJIT takes a different approach. It's a "textbook" compiler—uses SSA form, compiles whole methods, follows patterns you'd read about in a Dragon Book. Maxime Chevalier-Boisvert (who built YJIT) put it bluntly at RubyKaigi 2025: she didn't want to impose research risk on the Ruby community.
The practical result? ZJIT is faster than the interpreter today but not yet faster than YJIT. The team is targeting YJIT-level performance in Ruby 4.1. For now, stick with --yjit in production. But ZJIT's architecture opens the door for bigger optimizations down the road—and makes it easier for outside contributors to jump in.
To try it: build Ruby with --enable-zjit, then run with --zjit. Requires Rust 1.85.0+.
Ruby Box: Isolation Without Separate Processes
Ruby Box solves a problem you've definitely hit: monkey patches leaking everywhere. A test modifies String and suddenly your other tests fail randomly. A gem overrides something globally and your app breaks in production.
Ruby Box creates isolated containers within a single Ruby process. Code loaded in a box stays in that box. Monkey patches don't leak. Class definitions don't collide.
# Enable with RUBY_BOX=1 environment variable
box1 = Ruby::Box.new
box1.require("some_gem") # Isolated in box1
box2 = Ruby::Box.new
box2.require("some_gem") # Completely separate from box1
The team suggests three use cases: isolated test suites, blue-green deployments in a single process, and evaluating dependency updates side-by-side. This isn't security sandboxing—it's isolation for predictability.
Still experimental. Enable with RUBY_BOX=1. But the foundation is there.
Ractors Actually Work Now
Ractors launched in Ruby 3.0 with promise and problems. True parallelism, but the API was clunky and crashes were common.
Ruby 4.0 redesigns the whole communication model around Ractor::Port. Think of it like a mailbox. Any ractor can send to a port. Only the owner can receive.
port = Ractor::Port.new
Ractor.new(port) do |p|
p << "Message from worker"
end
message = port.receive # Only the creator can receive
The old Ractor.yield and Ractor#take methods are gone. New Ractor#join and Ractor#value mirror Thread behavior. The team also reduced global lock contention and improved CPU cache behavior.
The "experimental" warning still shows, but they're targeting stable status in 4.1.
Quality of Life Improvements
The small stuff adds up.
Set and Pathname are core classes now. No more require 'set'. Just use them.
# Ruby 4.0
fruits = Set["apple", "banana"] # Works immediately
path = Pathname("/usr/local") # Also just works
Logical operators continue the previous line. Finally.
# Now valid
result = condition_one
&& condition_two
&& condition_three
Better error messages. ArgumentError backtraces now show the receiver class. ErrorHighlight shows snippets for both the call site and method definition.
Unicode 17.0 support. Including Emoji 17.0.
Set#inspect returns something useful. Set[1, 2, 3] instead of #<Set: {1, 2, 3}>.
What Got Removed
Some cleanup happened:
--rjitflag is gone. RJIT moved to a separate gem.Ractor.yieldandRactor#takeremoved (replaced by Port API).ObjectSpace._id2refdeprecated.- CGI library dropped from default gems (only
cgi/escaperemains). - SortedSet no longer autoloaded—install the gem if you need it.
Should You Upgrade?
For most apps, yes. Ruby 4.0 maintains source compatibility. No breaking changes to your code unless you're doing something unusual with Ractors or relying on deprecated CGI methods.
My recommendation:
- Test with 4.0 now. Run your test suite. See what breaks (probably nothing).
- Keep using
--yjitin production. ZJIT isn't ready yet. - Ignore Ruby Box and Ractors unless you have specific use cases. They're building blocks for future features.
- Enjoy the Set/Pathname promotion. One less require statement.
The theme of Ruby 4.0 is infrastructure. The flashy performance wins came in 3.1-3.4 with YJIT. Now the team is laying groundwork for the next decade: better compilers, real isolation, actual parallelism.
That's the boring work that keeps languages alive for 30 more years.
Links:
- Ruby 4.0.0 Release Notes
- Ruby 4.0 Full Changelog
- ZJIT Proposal
- Rails at Scale Blog (Shopify's Ruby/Rails infrastructure team)