What Is New In Ruby 3.4

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 the RUBY_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 and RUBY_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 to true. 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 via RubyVM::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

Need help on your Ruby on Rails or React project?

Join Our Newsletter