What's new in Ruby 3.1?
The Ruby core team has a tradition of releasing a new version of Ruby on Christmas day, and last month we got Ruby 3.1.0. This release brings a new JIT compiler, a new debugger, improvements to IRB, better error messages, new hash syntax and lots of other improvements.
YJIT
The big news this year is YJIT,
the new JIT compiler developed at Shopify.
This has been merged into Ruby and is available using the --yjit
flag.
Ruby already has another JIT called MJIT
but it hasn’t translated to performance improvements in real world scenarios.
YJIT has shown more promise with realistic workloads like Rails codebases,
with over 20% faster performance in
railsbench.
Shorthand hash syntax
New syntactic sugar has been added, which allows omitting of the keys of a hash, or the keyword argument values for a method, when the keys have the same name as a variable or method in scope. There have been a few instances where I’ve wished for something like this recently, so I’m glad to see this syntax.
a = 1
b = 2
# Old
{ a: a, b: b } #=> { a: 1, b: 2}
# Ruby 3.1
{ a:, b: } #=> { a: 1, b: 2 }
This can be especially useful with keyword arguments:
# Old
foo(a: a, b: b)
# 3.1
foo(a:, b:)
Anonymous block forwarding
If you need to pass a block argument to another method,
it no longer needs to be named -
you can just use &
to refer to it.
Here’s a simple example of how you might use it:
def partition(list, &)
selected = list.select(&)
rejected = list.reject(&)
[selected, rejected]
end
evens, odds = partition([1,2,3,4,5]) { |n| n.even? }
evens #=> [2,4]
odds #=> [1,3,5]
Pin operator improvements
When pattern matching, the pin operator now supports expressions, including instance or class variables, and constants.
x = 42
case n
in ^x
puts "equal"
in ^(x * 2)
puts "double"
end
Class#subclasses
This new method returns the direct subclasses of the class.
In the example below,
Baz
is not included in Foo.subclasses
because it doesn’t directly inherit from Foo
.
class Foo; end
class Bar < Foo; end
class Baz < Bar; end
Foo.subclasses #=> [Bar]
New debugger
Ruby now comes bundled with the debug gem,
a new debugger that includes many features
that are provided by gems like
pry and byebug.
It can be invoked using the rdbg
executable
(example rdbg foo.rb
)
or you can add breakpoints similar to pry
by using binding.break
.
I’m a heavy user of pry,
so I’m excited to see
if rdbg can provide enough features of pry
to be able to replace it in my workflow.
Better error messages
Ruby now comes bundled with a gem called error_highlight
,
which shows much better error messages.
This is especially useful with those
“undefined method x on NilClass” messages.
For instance,
in the example below,
we can easily identify that the nil value
comes when we invoke []
on the variable json
rather than on json[:article]
.
$ ruby test.rb
test.rb:2:in `<main>': undefined method `[]' for nil:NilClass (NoMethodError)
title = json[:article][:title]
^^^^^^^^^^
IRB improvements
IRB now supports autocomplete,
and even shows the documentation
when you tab through the options.
For instance,
you could type ""
followed by a dot,
and you will see an autocomplete menu.
If you navigate to it using Tab,
you can see a snippet next to it.
You can also expand the documentation snippet
by hitting Alt+d
.
Final thoughts
This has been a great release for the Ruby community. Matz mentioned in his talk at Rubyconf 2021 that Ruby will now focus on implementation, performance and tooling rather than language enhancements for the near future, and this is evident in Ruby 3.1.
YJIT has already brought decent peformance improvements,
and promises even more in the future.
The new debugger and IRB improvements means
that we have a great alternative to pry
bundled with Ruby.
Although it’s been only a month since the release, next December’s Ruby 3.2 is already looking interesting. WebAssembly support has already been merged in. There are plans to rewrite YJIT in Rust, which could mean faster development on the JIT. These are exciting days for the Ruby community!
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.