What's new in Ruby 3.0?
Every year on Christmas day, the Ruby core team release a new version of Ruby. And this year, it’s a big one - Ruby 3.0 was just released! The big new features this time are support for static analysis and the new concurrency features. Here are some of the highlights of this release.
Ruby and types
RBS is the new language for describing types in Ruby programs. Type checkers like Sorbet, Steep or the newly introduced TypeProf can use these types to statically analyze the program. Here’s a simple example taken from RBS docs:
# Actual code
class User
attr_reader :login, :email
def initialize(login:, email:)
@login, @email = login, email
end
end
# RBS
class User
attr_reader login: String
attr_reader email: String
def initialize: (login: String, email: String) -> void
end
A new static type analyzer called TypeProf is now included with Ruby. To illustrate a very simple example, consider the following code:
def foo
'bar'
end
foo(12)
Running typeprof -v foo.rb
will generate the following output,
pointing out that
you’re sending an argument
to a method that doesn’t accept any.
# Errors
test.rb:5: [error] wrong number of arguments (given 1, expected 0)
# Classes
class Object
private
def foo: -> String
end
Ractors
Ruby’s global interpreter lock (GVL)
has stopped Ruby from
being able to use multiple cores in parallel.
To work around this,
a new actor-like concurrency primitive has been introduced,
called Ractor
.
Here’s a simple example from the docs, showing how message passing works with ractors. You can find more examples in the Ractor docs.
r1, r2 = *(1..2).map do
Ractor.new do
n = Ractor.recv
n.prime?
end
end
r1.send 2**61 - 1
r2.send 2**61 + 15
p r1.take #=> true
p r2.take #=> true
Rightward assignment
Experimental support has been added
for rightward assignment.
You can use =>
to assign
to a variable on the right,
but I can’t think of a reason
to use this over simple assignment.
However, this can be useful
for pattern matching
values in of arrays and hashes.
42 => x # same as x = 42
x = { a: 1, b: 2 }
x => { b: foo }
foo #=> 2
Find pattern matching
Pattern matching syntax has been extended to allow extracting elements from an array. Here’s a simple example:
case ['a', 1, 'b', 'c', 2, 'd', 'e', 'f', 3]
in [*pre, String => x, String => y, *post]
pre #=> ["a", 1]
x #=> "b"
y #=> "c"
post #=> [2, "d", "e", "f", 3]
end
Endless method definition
New syntax has been added
that allows you to skip the end
when the method body
contains a single expression.
When you need to extract
a one-line method in your code,
this concise syntax will come in handy.
def square(x) = x * x
Leading args in argument forwarding
Ruby 2.7 introduced argument forwarding
with ...
syntax.
However, you might want to access the leading arguments
and forward the rest.
Now the syntax has been updated to allow this:
def method_missing(method_name, ...)
return nil unless method_name.in?(allowed_methods)
object.send(method_name, ...)
end
Hash
Hash#transform_keys
has been updated
to accepts an argument
which lets you define which keys should be renamed.
You can also pass a block
which will be used to transform the remaining keys.
note = { title: 'Ruby 3.0', created_at: '2020-12-25' }
# without block
note.transform_keys({ created_at: :created })
# => { title: 'Ruby 3.0', created: '2020-12-25' }
# with block
note.transform_keys({ created_at: 'created' }, &:to_s)
# => { 'title' => 'Ruby 3.0', 'created' => '2020-12-25' }
Hash#except
which has been available
in Rails through ActiveSupport
is now included in the language itself.
x = { a: 1, b: 2, c: 3 }
x.except(:c)
#=> { a: 1, b: 2 }
Performance goals, aka Ruby 3x3
In 2015, Matz annnounce that Ruby 3 will be 3 times as fast compared to Ruby 2.0 (aka the 3x3 goal). The optcarrot benchmark. has long been used for comparing different versions of Ruby, and with JIT enabled, 3.0 did indeed reach the 3x goal.
MJIT is still not mature, and not all programs can take advantage of the speedup. Rails apps, for instance, actually perform worse with JIT enabled. However, this is still great news for CPU bound programs. With newly introduced concurrency features, the biggest performance wins from this release will be seen later on as more libraries make use of these features.
Further reading
This post only covers some of the features that I found the most interesting in this release. If you want to read further, the announcement and the changelog have more details.
The Ruby References page for Ruby 3.0 is the most comprehensive list of all the new features, and contains more details and code examples.
Is Ruby 3 Actually Three Times Faster? digs deeper into how much performance has improved in this version.