What's new in Ruby 3.3.0

What's new in Ruby 3.3.0

With the Ruby 3.3.0 release around the corner, we wanted to share a summary of the changes that are coming in this new version. There are many exciting new features and improvements!

Prism

Prism opens a new window is a new parser for Ruby code developed by Kevin Newton. The objective is to provide a portable and error tolerant parser that can be used by the different Ruby implementations, solving a major pain point all maintainers have to solve when new syntax is added to the language: update all parsers.

It also improves the experience of manipulating Ruby code programmatically, with a more clear API and representation of the parsed code.

The new parser is ready for production use and is already being used by other Ruby implementations, as well as tools and gems that deal with manipulating Ruby code.

During the month of December, Kevin has been writing about how Prism works in the Advent of Prism series opens a new window .

YJIT

YJIT opens a new window is a Just-In-Time compiler created by Shopify. It is already available in Ruby 3.2, and Ruby 3.3 comes with performance improvements in many areas and also reduces memory utilization along with other changes.

37signals has reported an 18% speed improvement for Basecamp opens a new window when they migrated to YJIT, and also reported that, with Ruby 3.3, 98% of their code is now executed by YJIT instead of the Ruby interpreter.

Performance

Apart from the performance benefits from YJIT, there are other areas with improvements in Ruby 3.3:

Garbage Collector

  • Handling of young objects was improved to reduce the number of GC collections.
  • A new REMEMBERED_WB_UNPROTECTED_OBJECTS_LIMIT_RATIO environment variable is now available to configure the ratio of unprotected objects that would trigger a GC collection. This allows for better fine-tuning of the GC to reduce the number of GC collections.
  • More classes use an optimized mechanism to make them faster to allocate and free memory along with more benefits.
  • Finally, more classes now have Write Barriers implemented, also making them faster to collect and reducing the number of collections.

It’s important to remember that when the Garbage Collector is performing a garbage collection, our application code is stopped. Therefore, the faster and less frequent the collection is, the better for our app’s performance.

performance Warning Category

A new category of warning is added in Ruby 3.3 to help us identify code that is known to not be performant. Currently, the only performance warning in the code can be found here opens a new window and it helps us identify when new instance variables are added to objects with a shape to warn us that it will impact the speed of accessing them.

You can read more about Ruby warnings in my article Exploring Ruby Warnings opens a new window .

Other Changes

  • When using YJIT, a new RubyVM::YJIT.enable method is introduced. This allows us to disable it during boot (with the --disable-yjit --disable-rjit command line flags), and then enable it once the application starts to speed up the boot process.
  • defined?(@ivar) is optimized with Object Shapes.

Use Lrama Instead of Bison

Starting with Ruby 3.3, the Lrama parser generator opens a new window is used instead of Bison opens a new window . This makes it easier to maintain consistency to avoid having to deal with different versions of Bison in different systems.

RJIT

RJIT is an experimental Just-In-Time compiler implemented in pure Ruby code. The benefit of this JIT compiler is that it removes the need of a C compiler when working with gems that make use of native extensions.

This is not meant for production usage yet, it only supports the x86_64 architecture in Unix platforms.

M:N Thread Scheduler

A new thread scheduler is introduced so applications using Ractors can use more threads in all available cores more efficiently. You can read more about this new feature in the issues tracker opens a new window .

Changes in Environment Variables

RUBY_GC_HEAP_INIT_SLOTS has been deprecated and is a no-op. The RUBY_GC_HEAP_{0,1,2,3,4}_INIT_SLOTS environment variables should be used instead.

A new RUBY_CRASH_REPORT environment variable was introduced to allow redirecting Ruby crash reports to a file or subcommand.

The RUBY_MN_THREADS and RUBY_MAX_CPU environment variables can be used to configure the M:N scheduler.

REMEMBERED_WB_UNPROTECTED_OBJECTS_LIMIT_RATIO is added to fine-tune the garbage collector as previously mentioned.

IRB

Many improvements are coming with the update of the irb gem, including commands showing more information, better debugging experience, and experimental autocompletion of code using type information provided by Sorbet or RBS.

Deprecations

  • Process::Status#& and Process::Status#>> are deprecated. The deprecation message will include a suggested method in each case to use as a replacement, since there’s not just one option.
  • Refinement#refined_class is deprecated in favor of Refinement#target, and will be removed in Ruby 3.4.
  • Subprocess creation/forking via the following file open methods is deprecated is favor of IO.popen:
    • Kernel#open
    • URI.open
    • IO.binread
    • IO.foreach
    • IO.readlines
    • IO.read
    • IO.write
  • it calls without arguments in a block with no ordinary parameters are deprecated with no replacement. it will be a reference to the first block parameter in Ruby 3.4.
  • While not technically a deprecation, ext/readline is retired in favor of the new pure-Ruby implementation called reline. This simplifies the installation of Ruby by not requiring system libraries like libreadline or libedit.

Removal of deprecated behavior

We always recommend addressing all deprecated code before jumping to the next version of Ruby. So be sure to run your app with Ruby 3.2 and deprecated warnings turned on (check Exploring Ruby Warnings opens a new window ).

  • Encoding#replicate has been removed. The replacement is to use the original encoding class instead.
  • When given a non-lambda, non-literal block, Kernel#lambda now raises ArgumentError instead of returning it unmodified.
  • MJIT is removed. Also the --disable-jit-support command line flag is removed. Consider using --disable-yjit --disable-rjit instead.

Default and Bundled Gems

Like with every Ruby release, we have a long list of default and bundled gems that are updated to new versions. The complete list can be found here opens a new window .

One notable change is that if an application depends on the racc gem opens a new window , it now has to be added to the Gemfile.

Conclusion

Ruby 3.3 brings performance improvements in many areas: faster and less frequent garbage collection, memory optimizations, faster code, and YJIT improvements. And the best part is that, in most applications, these performance improvements will be noticeable without any modification of the code!

Apart from that, many new features are added:

  • Prism is one of the most exciting additions, not just for MRI but for the whole Ruby community.
  • IRB’s autocompletion based on types can help improve the development experience.
  • M:N thread scheduler for applications using Ractor means more efficient use of the CPU.
  • The new RUBY_CRASH_REPORT env variable is great for monitoring.

We cannot list every single change here, so we recommend reading the following list of resources to go into the details:

Are you ready to upgrade to Ruby 3.3? We can help you get there! opens a new window

Get the book