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:

  • --rjit flag is gone. RJIT moved to a separate gem.
  • Ractor.yield and Ractor#take removed (replaced by Port API).
  • ObjectSpace._id2ref deprecated.
  • CGI library dropped from default gems (only cgi/escape remains).
  • 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:

  1. Test with 4.0 now. Run your test suite. See what breaks (probably nothing).
  2. Keep using --yjit in production. ZJIT isn't ready yet.
  3. Ignore Ruby Box and Ractors unless you have specific use cases. They're building blocks for future features.
  4. 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: