Christmas is approaching, along with a new version of Ruby! The first release is available as of 12/12/2024 and here is your gift: a sneak peek at what’s new in Ruby 3.4.
What’s new in the language
Warning on frozen_string_literal
The plan for Ruby is to make all string literals frozen by default. This represents a step towards that goal.
# filename: frozen_string_literal.rb
name = "John"
name << " Doe"
ruby -W:deprecated frozen_string_literal.rb
will raise the following warning:
warning: literal string will be frozen in the future (run with –debug-frozen-string-literal for more information)
You can enforce this behavior by running Ruby with the --enable=frozen-string-literal
flag. With this flag, the above
code will raise a FrozenError
.
can’t modify frozen String: "John" (FrozenError)
it, the new reference block parameter
it
is an alternative to the numbered parameter _1
.
[1, 2, 3].map { it ** 2 } # => [1, 4, 9]
This makes the code more readable and reduces the likelihood of errors.
**nil is a valid syntax for keyword splatting
This one has been around for a while. It’s for handling cases like the one below:
invitation = { invitation_attributes: params.slice(:inviter_id) } if params.key?(:inviter_id)
User.create(
email: 'john.doe@ruby.example',
first_name: 'John',
first_name: 'Doe',
**invitation, # when `invitation` is nil, it evaluates to **nil that's now treated as **{}
)
Checking if invitation
is nil
is no longer necessary.
Error messages and backtrace have been changed (and they are better)
Refer to the code provided below:
def raise_nested_exceptions
raise "First error"
rescue
begin
raise "Second error"
rescue
raise "Third error"
end
end
raise_nested_exceptions
in Ruby 3.3, it raises an error like this:
sample.rb:7:in `rescue in rescue in raise_nested_exceptions': Third error (RuntimeError)
from sample.rb:4:in `rescue in raise_nested_exceptions'
from sample.rb:1:in `raise_nested_exceptions'
from sample.rb:11:in `<main>'
sample.rb:5:in `rescue in raise_nested_exceptions': Second error (RuntimeError)
from sample.rb:1:in `raise_nested_exceptions'
from sample.rb:11:in `<main>'
sample.rb:2:in `raise_nested_exceptions': First error (RuntimeError)
from sample.rb:11:in `<main>'
In Ruby 3.4 some changes have been made to make it more readable.
- Backtick (`) on the left side was replaced with a single quote (‘).
- Class names are displayed before method names: in a huge code base, potentially with multiple methods with the same name, finding the right was difficult. This change makes it easier to locate the right method.
- Extra frames from
rescue
/ensure
blocks are removed: this makes the backtrace shorter and easier to read.
With all these changes, the backtrace now looks like this:
sample.rb:7:in 'Object#raise_nested_exceptions': Third error (RuntimeError)
from sample.rb:11:in '<main>'
sample.rb:5:in 'Object#raise_nested_exceptions': Second error (RuntimeError)
from sample.rb:11:in '<main>'
sample.rb:2:in 'Object#raise_nested_exceptions': First error (RuntimeError)
from sample.rb:11:in '<main>'
What’s new in core classes
Hash.new now accepts a capacity argument
Reallocating data structures can be costly. When you know the size of the structure in advance you can save memory roundtrips by preallocating the structure.
That’s what capacity
is for. You can now create a Hash with a predefined capacity.
hash = Hash.new(capacity: 10)
In the above example, capacity
represents the quantity of buckets that the hash initially starts with. This helps improve performance by reducing the need to resize the hash as you add items.
Introduced GC.config
A new method GC.config
has been introduced to configure the garbage collector. Along with this, a new configuration
parameter that can turn on/off GC Major executions.
GC.config(rgengc_allow_full_mark: false)
This is a useful technique to reduce latency. See how pausing GC might be useful.
Implementation improvements
Default parser is now Prism
Prism is a handwritten parser designed with a focus on error tolerance. This means it can parse code that contains syntax errors while still delivering a meaningful error message.
In an era dominated by VSCode and Language Server Protocols (LSPs), this represents a significant improvement. It enhances the overall development experience.
Array methods were rewritten in Ruby
Array#each
, Array#map
, and Array#select
have been rewritten in Ruby.
This allows a Ruby-to-Ruby communication – less expensive than a Ruby-to-C communication. Having these methods in Ruby makes them eligible for JIT optimization making them faster.
A microbenchmark made on Array#each
, for instance, shows it is 7x faster.
What else?
- Alternative Garbage Collector implementations can be loaded at runtime.
- YJIT optimizations reduce memory usage and improve performance.
- An optional
Fiber::Scheduler#blocking_operation_wait
hook allowing blocking operations to be moved out of the event loop. require
can now be used inReactors
.
In this article, we covered some of the most important changes in Ruby 3.4. There are a few more that you can check out in the official release notes.
Conclusion
This version brings many improvements. Having standard libraries rewritten in Ruby is now a great advantage; not only for performance but also for maintainability. Moreover, it makes it easier for the community to contribute to Ruby.
Changes in the language were also very welcome. The new it
reference block parameter is a great addition to the language. It makes the code more readable and less prone to errors. With improved error messages and backtraces, debugging will be much easier.
Programming in Ruby is enjoyable and these changes make it even better. I’m excited to see what the future holds for Ruby.
Happy coding and count on Codeminer42 if you need help migrating or introducing your team to Ruby 3.4!
We want to work with you. Check out our "What We Do" section!