Nithin Bekal Posts About

What's New in Ruby 3.0?

27 Dec 2020

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.

For my posts about previous versions of Ruby, see here: 2.7, 2.6, 2.5, 2.4, 2.3.

Nithin Bekal
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. I'm @nithinbekal on Twitter.