Nithin Bekal About

What's new in Ruby 3.2

10 Jan 2023

Ruby 3.2 was released on Christmas day, and I’ve been playing around with its new features. The highlights this year are the performance gains from YJIT, WebAssembly support, faster regular expressions, and a new way to define immutable value objects,

(This is only a list of things I found most interesting about this release. If you’re looking for a more complete list, take a look at the release announcement.)

YJIT is now production ready (and really fast!)

YJIT, the new JIT compiler for Ruby, was released last year as an experimental feature. Now it’s been marked production ready, and brings even more performance gains. Here are a couple of reads about real world performance gains:

YJIT was also completely rewritten in Rust in order to make it easier to maintain. This allowed the team to quickly add support for ARM. This means that you can now run YJIT on your M-series macs or a Raspberry Pi.

Immutable objects with Data.define

The newly introduced Data class allows you to define immutable value objects.

# Create a Point data class
Point = Data.define(:x, :y)

# Instantiate a point object
p = Point.new(x: 3, y: 7)

This might seem familiar because we already have Struct.new which defines similar classes. However, Data classes are immutable, so don’t have setters for the attributes.

point.x = 42
#=> undefined method `x=' for #<data Point x=3, y=7> (NoMethodError)

Data also defines the deconstruct and deconstruct_keys methods so that the objects can be pattern matched like this:

case point
in Point(0, 0)
  puts "Origin"
in Point(x, y) if x + y > 10
  puts "Too far away"
in Point(x, y)
  puts "(#{x}, #{y})"
end

Struct with keyword args

There’s a small usability improvement with structs as well. The keyword_init: true argument is no longer needed to initialize structs with keyword args.

# Ruby 3.1
Point = Struct.new(:x, :y, keyword_init: true)

# Ruby 3.2
Point = Struct.new(:x, :y)

Point.new(x: 2, y: 3)
#=> #<struct Point x=2, y=3>

Regular expressions

There are improvements to Regex, mainly to prevent ReDOS (regexp denial-of-service) attacks. Firstly, the regexp matching algorithm has been improved so that many matches will complete in linear time. For regexes that can’t use this optimization, you can also set a timeout so that a Regexp::TimeoutError is raised if the match exceeds that time.

# Set a 2s timeout for a single regex
pattern = Regexp.new(/foo/, timeout: 2)
"foo".match(pattern)

# Or set a global timeout
Regexp.timeout = 5

IRB

One of my favorite things about recent releases of Ruby is the focus on tools like IRB. Ruby 3.2 bundles the latest version 1.6 of IRB. This brings support for a lot of the debugging commands that you might have seen in gems like pry and byebug. Stan Lo, who worked on many of these features has written in detail about the new features of IRB.

As someone who has always used pry and pry-byebug, I’m excited about these changes. We will be able to get most of the benefits of those tools out of the box with Ruby.

Better error messages

syntax_suggest has been integrated into Ruby, and points out missing or extra ends in your code. This functionality was previously in a gem called dead_end, but it was merged into Ruby itself.

Unmatched `end', missing keyword (`do', `def`, `if`, etc.) ?
> 2  def foo
> 4  end
> 5  end
test.rb:5: syntax error, unexpected `end' (SyntaxError)

The builtin error_highlight gem now highlights the relevant argument for TypeError and ArgumentError.

test.rb:3:in `+': nil can't be coerced into Integer (TypeError)

bar = 1 + foo[5]
          ^^^^^^
        from test.rb:3:in `<main>'

WebAssembly support

Ruby now supports WebAssembly, which means that Ruby can now run in browsers! You can already see this in action in the Try Ruby playground. Another example is Ruby Syntax Tree, which converts Ruby code into s-expressions. (Kevin Newton has written more about it here).

Other changes

  • Set is now a builtin class, and you no longer need to require "set" to use it.
  • bundle gem now supports --ext=rust to allow building gems with rust extensions. (details here)

Further reading

With these posts, I try to highlight some of the changes that I found most interesting, and often skip over features I might not use. If you’re looking for a more comprehensive look at the release, I highly recommend looking at the release announcement, changelog, and the excellent Ruby References website, which covers all the new features in detail with lots of examples.

Other links

This article is part of the What's New in Ruby series. To read about a different version of Ruby, pick the version here:

2.3    2.4    2.5    2.6    2.7    3.0    3.1    3.2    3.3

Hi, I’m Nithin! This is my blog about programming. Ruby is my programming language of choice and the topic of most of my articles here, but I occasionally also write about Elixir, and sometimes about the books I read. You can use the atom feed if you wish to subscribe to this blog or follow me on Mastodon.