What Is New In Ruby 4.0

Ruby 4.0 is here, releasing on Christmas Day 2025, marking 30 years since Ruby’s first public release. This release packs some genuinely exciting features.

Let’s explore the most impactful changes in Ruby 4.0.

ZJIT - A New JIT Compiler

Ruby 4.0 introduces ZJIT, a new just in time compiler built by the same team behind YJIT. Unlike YJIT’s lazy basic block versioning approach, ZJIT uses a more traditional method based compilation strategy.

The key difference? ZJIT is designed to be more accessible to contributors. It follows a “textbook” compiler architecture that’s easier to understand and modify.

To enable ZJIT, we can use the --zjit flag:

ruby --zjit my_script.rb

While ZJIT is faster than interpreted code, YJIT remains the recommended choice for production. ZJIT sets the foundation for future performance improvements and community contributions.

Ruby::Box - Isolated Namespaces

Ruby::Box is an experimental feature that brings isolated namespaces to Ruby. This allows us to load multiple versions of a library simultaneously.

Enable it with the RUBY_BOX=1 environment variable:

# Load two different versions of a gem

box1 = Ruby::Box.new
box1.require("some_gem", version: "1.0")

box2 = Ruby::Box.new
box2.require("some_gem", version: "2.0")

This is particularly useful for:

  • Testing library upgrades without conflicts
  • Running legacy code alongside modern dependencies
  • Isolating third party code

The syntax is still evolving, so expect improvements in future releases.

Redesigned Ractor API

Ractors, Ruby’s answer to true parallelism, received a significant API overhaul. The new design uses Ractor::Port for communication between ractors.

# Ruby 4.0 - New Ractor::Port API

port = Ractor::Port.new

r = Ractor.new(port) do |p|
  p.send("Hello from Ractor!")
end

message = port.receive
puts message

The old Ractor.yield and Ractor#take methods have been removed. This change mirrors inter process communication (IPC) semantics, making the API more intuitive for developers familiar with concurrent programming patterns.

Logical Operators on Next Line

Ruby 4.0 now allows logical operators to appear at the beginning of a new line:

# Now valid in Ruby 4.0

result = condition_one
  && condition_two
  && condition_three

# Also works with 'and', 'or', '||'

value = first_option
  || second_option
  || default_value

This improves readability for complex conditional expressions and aligns with common formatting preferences.

instance_variables_to_inspect

When debugging, inspect can get noisy with memoization variables. Ruby 4.0 introduces instance_variables_to_inspect to control what shows up:

class Rectangle
  def initialize(width, height)
    @width = width
    @height = height
  end

  def area
    @area ||= @width * @height
  end

  def instance_variables_to_inspect
    [:@width, :@height]
  end
end

rect = Rectangle.new(10, 5)
rect.area
puts rect.inspect
# => #<Rectangle @width=10 @height=5>

# @area is hidden from output

This keeps inspect output clean and focused on the essential state.

Set Is Now a Core Class

Set has been promoted from stdlib to a core class. No more require 'set' needed:

# Works directly in Ruby 4.0

fruits = Set["apple", "banana", "cherry"]
fruits.add("mango")

# inspect now returns eval friendly output

fruits.inspect
# => Set["apple", "banana", "cherry", "mango"]

# Previously: #<Set: {"apple", "banana", "cherry", "mango"}>

Note: SortedSet has been removed. So we will need to install the sorted_set gem if/when required.

Pathname Promoted to Core

Pathname is now a core class instead of a default gem. This means better integration and no separate gem management:

# Available without require

path = Pathname.new("/home/user/documents")
path.children.each { |child| puts child.basename }

Extended source_location

Method#source_location, Proc#source_location, and UnboundMethod#source_location now return 5 elements instead of 2:

def greet(name)
  "Hello, #{name}!"
end

method(:greet).source_location
# => ["/path/to/file.rb", 1, 0, 2, 3]

# [path, start_line, start_column, end_line, end_column]


# For backward compatibility

method(:greet).source_location.take(2)
# => ["/path/to/file.rb", 1]

This is great for tooling and IDE integrations that need precise location info.

String#strip with Selectors

String#strip and its variants now accept selector arguments:

text = "  \t Hello World \n  "

# Strip specific characters

text.strip(" ", "\t", "\n")
# => "Hello World"

New Math Methods

Ruby 4.0 adds Math.log1p and Math.expm1 for more precise calculations with small numbers:

# log1p(x) computes log(1 + x) more accurately for small x

Math.log1p(0.0001)
# => 0.00009999500033330834


# expm1(x) computes exp(x) - 1 more accurately for small x

Math.expm1(0.0001)
# => 0.00010000500016667084

These are essential for scientific computing and financial calculations.

Other Notable Changes

  • *nil no longer calls nil.to_a (consistent with **nil behavior)
  • IO.select accepts Float::INFINITY as timeout
  • File::Stat#birthtime now works on Linux (via statx syscall)
  • Socket.tcp accepts open_timeout: keyword argument
  • Ractor#join and Ractor#value added (similar to Thread)
  • RJIT has been removed (YJIT and ZJIT remain)
  • Unicode updated to version 17.0

Wrapping Up

Ruby 4.0 is a significant release that lays groundwork for the future. ZJIT opens doors for community driven compiler improvements, Ruby::Box experiments with namespace isolation, and the Ractor redesign makes parallel programming more intuitive.

For most applications, upgrading should be smooth since there are no major breaking changes.

References:

Need help on your Ruby on Rails or React project?

Join Our Newsletter