Alright, you know the drill. The Ruby team do the hard work of putting together a new version packed with features and I pick my favourites and write about them.
If you’d like to read the full set of changes, they’re over on the Ruby project website. There’s plenty more good stuff in there—these were just the changes that stood out to me.
Default block parameter name
In many situations where we pass a block in Ruby, that block receives a single argument (each, map, filter, etc). A lot of the time, we end up giving that argument a fairly generic name like item because we know that we’re working through items in a list.
[
“beige chinos”,
“blue jorts”,
“rainbow jorts”,
].filter { |item| item =~ /jorts/ }
# => [“blue jorts”, “rainbow jorts”]
In Ruby 3.4, it has been added as a default name for the first parameter passed to a block. Rather than specifying a name in trivial cases like the one above, you can now write:
[
“beige chinos”,
“blue jorts”,
“rainbow jorts”,
].filter { it =~ /jorts/ }
# => [“blue jorts”, “rainbow jorts”]
Better connection handling: “Happy Eyeballs Version 2”
I’ll be honest: the reason this made my list this year is that seeing it in the changelog pointed me to an interesting RFC that I hadn’t heard of before! I spent a couple of hours down a rabbit hole reading RFCs and a few snippets of source code and I thought it was worth talking about.
Ruby 3.4 adds an implementation of RFC8305 (Happy Eyeballs Version 2: Better Connectivity Using Concurrency) to its TCP socket implementation.
In a nutshell, the RFC is an attempt at describing a way for clients to handle multiple IP addresses being returned by DNS queries, particularly in the context of a dual-stack (IPv4 and IPV6) network. It describes how to make the DNS requests1, how to order the addresses returned by them, and how to kick off concurrent connection attempts to those addresses in a way that balances giving the user a response quickly with not creating pointless load on servers2.
RFC8305 builds on the earlier RFC6555. RFC6555 talked in relatively high-level terms about what’s needed in an algorithm that uses a hostname to establish a connection in a dual-stack environment and made reference to an approach shared by Mozilla Firefox and Google Chrome at the time. RFC8305 goes beyond that and provides much more concrete descriptions of what should be done at each stage.
One thing I really like about their approach is that they provide sensible default values for each of the tunable parameters of their algorithm, which were reached through empirical measurement of connection timings across a wide range of devices. A company with the reach of Apple, where this RFC originated, is well-placed to take measurements at the scale needed to find widely applicable defaults. It’s great to see the effort put into making the output of their work useful beyond the walls of their company.
I’m less bullish about the IPv6 transition than some of my friends. If that transition is going to be successful, it involves an unavoidable period of running dual-stack networks3. When doing so caused trouble on my home network, I switched IPv6 off rather than trying to debug it4. If we want people to embrace dual-stack networks—and ultimately IPv6—we need socket libraries that do the right thing by default.
Clearer exception backtraces
Okay, we’re done with the rabbit hole and we’ve got a simple one to round things off.
In Ruby 3.4, exception backtraces include the method owner (class or module) as well as the name of the method that an exception was raised from.
Let’s look at some code that raises an exception:
module Foo
class Bar
def inspect
1 + ‘1’
end
end
end
p Foo::Bar.new
In Ruby 3.3 and earlier, this would print a backtrace like:
/tmp/foo.rb:4:in `+’: String can’t be coerced into Integer (TypeError)
from /tmp/foo.rb:4:in `inspect’
from /tmp/foo.rb:9:in `p’
from /tmp/foo.rb:9:in `’
Ruby 3.4 prints out the name of the module/class that contains each of the methods in the stack track, like:
/tmp/foo.rb:4:in `Integer#+’: String can’t be coerced into Integer (TypeError)
from /tmp/foo.rb:4:in `Foo::Bar#inspect’
from /tmp/foo.rb:9:in `Kernel#p’
from /tmp/foo.rb:9:in `’
It’s a small change, but I’m a big believer in anything that makes it quicker to understand what a piece of code is doing. I’m glad to see that the Ruby team still values developer experience so highly. It’s one of the best parts of writing the language.
That’s all from me. If you enjoyed this post, do get in touch with your own favourite feature from Ruby 3.4 and the best cheese you’ve eaten this Christmas5.
You can find me on Mastodon or Bluesky. ✌🏻💖🎄
GIPHY App Key not set. Please check settings