It’s official, Ruby 3.4 first release is available, bringing a wave of excitement to the Ruby community.
In this blog, we will go through the latest features, enhancements, and bugfixes introduced in the Ruby 3.4
Prism is the new default parser
Ruby 3.4 switches the default parser from parse.y
to Prism, which was introduced in Ruby 3.3 as a new parser designed for better maintainability, error tolerance,
and performance.
To use the conventional parser, use the command-line argument --parser=parse.y
. Feature #20564.
Garbage Collection
Ruby 3.4 introduced several notable features and enhancements related to its garbage collection (GC) system, aimed at improving performance and flexibility. Here are the key updates
-
Ruby 3.4 allows us to dynamically load different GC implementations. Enable it by configuring Ruby with
--with-modular-gc
at build time and load GC libraries at runtime with theRUBY_GC_LIBRARY
environment variable. Feature #20351. -
The default GC is now separated into its own library, which can be built and enabled using
make modular-gc MODULAR_GC=default
andRUBY_GC_LIBRARY=default
. Feature #20470 -
Ruby 3.4 provides an experimental GC based on the Memory Management Toolkit (MMTk), which requires Rust to build. Enable it with
RUBY_GC_LIBRARY=mmtk
. Feature #20860 -
A new method
GC.config
has been introduced to configure the garbage collector. Along with this, a new configuration parameter that can turn on/off GC Major executions and by default, it is set totrue
. Feature #20443
GC.config(rgengc_allow_full_mark: false)
YJIT
YJIT in Ruby 3.4 offers better performance across x86-64
and arm64
platforms, reduced memory usage for compilation metadata, and increased stability with multiple bug fixes.
New Features:
-
The unified memory limit, set with
--yjit-mem-size
(default 128MiB), replaces the older--yjit-exec-mem-size
to provide a clear, total memory limit for YJIT. -
Enhanced runtime stats are now always available via
RubyVM::YJIT.runtime_stats
. -
Track YJIT compilation progress with
--yjit-log
. The log is also accessible at runtime viaRubyVM::YJIT.log
. -
YJIT now supports shareable constants in multi-ractor mode.
-
Use
--yjit-trace-exits=COUNTER
to trace counted exits.
New Optimizations:
-
Compressed context reduces memory needed to store YJIT metadata
-
Improved allocator now supports allocating registers for local variables.
-
Optimized Ruby core methods including array methods (
Array#each
,Array#select
,Array#map
) have been rewritten in Ruby for better performance when YJIT is enabled.Feature #20182. -
Small, trivial methods like empty methods, constant-returning methods, self-returning methods and argument returning methods are now inlined.
frozen_string_literal is enabled by default
Now in Ruby 3.4, string literals in files without a # frozen_string_literal: true
comment now
behave as if they were frozen. If they are mutated, a deprecation warning is emitted. Refer here.
But the warnings are not emitted by default, we have to enable it by setting Warning[:deprecated] = true
. The reason is deprecation warnings should only be useful
during development for updating Ruby code,
and should not be displayed continuously.
To disable the behavior where string literals are treated as frozen by default in Ruby, we can use the --disable-frozen-string-literal
command line
argument when running the Ruby script.
Keyword splatting **nil
Ruby 3.4, no more raises TypeError
when double splat **
operator used with nil
, it is treated similarly to **{}
, which passes no keywords
and does not call any conversion methods. Refer here.
def example_method(**args)
puts "args are: #{args}"
end
example_method(**nil)
# Before
# => no implicit conversion of nil into Hash (TypeError)
# => args are: {}
Range#size
With Ruby 3.4, Range#size
raises TypeError
if the range is not iterable i.e. begin value of Range is a Float or Rational or Beginless. Refer here
(0.51..5).size #=> can't iterate from Float (TypeError)
(5.quo(2)..10).size #=> can't iterate from Rational (TypeError)
(..1).size #=> can't iterate from NilClass (TypeError)
Block as argument
Ruby 3.4 throws SyntaxError
while passing block as argument in index as it is no longer allowed. Refer here
numbers = []
even_block = Proc.new { |i| i.even? }
def numbers.[]=(*args, &block)
self.concat(args.flatten)
end
numbers[&even_block] = 10
#=> block arg given in index (SyntaxError)
it - the default block parameter
Previously, Ruby 2.7 introduced numbered parameters intended to simplify writing short blocks.
["Ruby", "on", "Rails"].each { puts _1 }
Ruby 3.4 makes it
as default block parameter for more readability. Refer here
["Ruby", "on", "Rails"].each { puts it }
Keyword arguments
Ruby 3.4 throws SyntaxError
while passing keyword arguments in index to an array set methods as it is no longer allowed. Refer here
projects[1, status: "completed"], _ = ["Project A updated", "Project B"]
=> keyword arg given in index (SyntaxError)
Exception#set_backtrace
The Exception#set_backtrace
now accepts arrays of Thread::Backtrace::Location. Kernel#raise, Thread#raise
and Fiber#raise also accept this new format. Feature #13557.
Error messages and backtrace displays have been changed.
-
Use a single quote instead of a backtick as a opening quote. Feature #16495
-
Display a class name before a method name (only when the class has a permanent name). Feature #19117
Before:
test.rb:1:in `foo': undefined method `time' for an instance of Integer
from test.rb:2:in `<main>'
After:
test.rb:1:in 'Object#foo': undefined method 'time' for an instance of Integer
from test.rb:2:in '<main>'
Refer