What's New in Ruby 3.2
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:
- Ufuk Kayserilioglu has written about deploying a prerelease version of Ruby 3.2 with YJIT to render Shopify storefronts, and seeing 10% speedups.
- Peter Solnica has benchmarked the dry-rb libraries, and had some impressive numbers to share there.
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 end
s 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 torequire "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
- Year in Review 2022: Tenderlove’s Ruby and Rails Reflections and Predictions
- It is not what you expect, but it is what you want: how Data#initialize is designed
For my posts about previous versions of Ruby, see here: 3.1, 3.0, 2.7, 2.6, 2.5, 2.4, 2.3.