According to Ruby tradition, the core team will release Ruby 3.4 on December 25—but the preview has been out since May! If you're anticipating the release like we are, you're probably wondering what's new in Ruby 3.4. There aren't any huge changes in this release, but you'll want to be aware of a few really cool things.
Let's dig into some of the language changes first.
Language changes
Language changes are the most immediately relevant changes to most Ruby developers. Let's take a quick look at each of them!
Frozen string literals
If you've written Ruby for even a little while, you've probably seen a file that starts with:
# frozen_string_literal: true
The example above is a magic comment, and it actually has meaning to the Ruby interpreter! This comment tells the interpreter to treat each string in this file as if it had freeze
called on it. If you try to modify a String in a file with this magic comment, you'll get a runtime error.
In Ruby 3.4, strings will act as if they're frozen by default. An attempt to mutate them will result in a deprecation warning instead of an error. A future version of Ruby will enforce the default frozen string literals by raising an exception if you try to mutate them. This transitionary period will give Ruby developers time to migrate their apps.
Default block parameter
Ruby 3.4 introduces a default block parameter to make short code blocks cleaner.
Before Ruby 2.7, printing each element of an array with the each
method looked something like:
[1, 2, 3].each { |item| puts item }
Ruby 2.7 introduced numbered parameters to remove the need to name them. After Ruby 2.7, you could write the same code as:
[1, 2, 3].each { puts _1 }
Ruby 2.7's improvement is more straightforward but not exactly clear. If you're unfamiliar with this Ruby syntax, you may struggle to understand what's happening here. Ruby 3.4 is introducing an even better way to do this same thing:
[1, 2, 3].each { puts it }
Keyword splatting nil
In Ruby versions before 3.4, using the double splat operator (**
) with nil
raised a TypeError
. This sort of made sense because nil
could not be implicitly converted into a hash.
Ruby 3.4 provides a simple change to this - it implicitly converts nil
into a hash. Calling **
on nil will be like calling **
on an empty hash.
Core classes updates
The core class updates aren't as immediately relevant to Ruby developers as the language changes, but they're definitely worth understanding!
Exception#set_backtrace
A feature request on the Ruby issue tracker described setting the backtrace with an array of strings before this change in Ruby 3.4 as "lossy." The exception previously returned nil on #backtrace_locations
.
In Ruby 3.4, Exception#set_backtrace
will accept a Thread::Backtrace::Location
array so that you can rebuild a Backtrace
instance and have a fully functioning Exception.
Range#size
Range#size
also has new behavior in Ruby 3.4. If the range that size
is being called on is not iterable, Ruby will now throw a TypeError
. This is a small change to behavior that you'll only notice if you're calling size
on a small subset of ranges.
Other changes new in Ruby 3.4
Another small change in Ruby 3.4 is to methods that get passed to a block they don't use. When running in verbose mode, Ruby will now throw a warning if you pass a block to a method that doesn't use the block.
You may also be happy to hear that Ruby 3.4 improves the performance of Array.each
due to an implementation rewrite!
Lastly, another update is to how error messages and backtraces are displayed. Ruby will now use single quotes instead of backticks when relevant and display a class name before a method name.
Upgrading to Ruby 3.4
Upgrading to Ruby 3.4 shouldn't be a major lift for most folks. The most labor-intensive part will be removing all those unneeded frozen string magic comments! Taking the time to upgrade to take advantage of all that's new in Ruby 3.4 will pay dividends, so don't delay!
And don't forget—to get more Ruby news and even tutorials like this one in your inbox, sign up for the Honeybadger newsletter!