Skip to content
← All posts
4 min read Dawid Skłodowski

Ruby 2.5: rescue in blocks and a faster boot

Ruby 2.5.0 lands on Christmas Day with a clutch of practical ergonomics — rescue/ensure directly in do/end blocks, yield_self, Hash#slice — plus faster startup and block-passing. A practical tour.

Ruby 2.5.0 has arrived on Christmas Day, continuing the pattern of recent releases: a handful of small, practical ergonomic improvements you will use immediately, plus quiet performance work under the hood. Nothing here reshapes the language, but several changes remove daily friction, and the performance gains are free. Here is the tour.

rescue and ensure directly in blocks

The most-welcome change cleans up something that has annoyed Rubyists for years. Previously, to rescue an exception inside a do...end block, you needed an explicit inner begin:

# before 2.5 — the awkward inner begin
[1, 2, 3].each do |n|
  begin
    risky(n)
  rescue => e
    log(e)
  end
end

2.5 lets you put rescue, else, and ensure directly in the block, no begin needed — exactly as you can in a method body:

# Ruby 2.5 — rescue right in the block
[1, 2, 3].each do |n|
  risky(n)
rescue => e
  log(e)
end

It is a small thing, but blocks are everywhere in Ruby, and handling errors inside them is common, so this removes a recurring little wart and an extra level of indentation. It also makes the block read like a method, which is the right mental model.

yield_self: a pipe for any object

A quietly powerful addition is Object#yield_self, which yields the receiver to a block and returns the block’s result. It lets you chain transformations on a value without temporary variables, in a left-to-right pipeline:

# without it — a temp variable, or nested calls read inside-out
url = build_url(params)
response = fetch(url)
data = parse(response)

# with yield_self — a readable pipeline
data = params.yield_self { |p| build_url(p) }
             .yield_self { |u| fetch(u) }
             .yield_self { |r| parse(r) }

It is Ruby’s version of a pipe operator, and it shines when you want to express “take this value, then do this, then this” as a flowing chain rather than a stack of temporaries or inside-out nesting. (The name is a touch clunky — a shorter alias, then, arrives in a later version — but the capability is here now and genuinely useful.)

Hash#slice and friends

A trio of small methods that remove boilerplate you have written a hundred times. Hash#slice returns a hash with only the given keys — perfect for whitelisting:

params.slice(:name, :email)          # only these keys, the rest dropped

Alongside it, String#delete_prefix and String#delete_suffix do exactly what they say, replacing the fiddly sub(/\A.../, "") regexes:

"image.png".delete_suffix(".png")    # => "image"
"/api/users".delete_prefix("/api")   # => "/users"

None of these is profound, but each replaces a small, slightly-error-prone idiom with a clear method — the kind of polish that makes everyday code read better.

Performance: faster boot and faster blocks

Under the hood, 2.5 brings two performance wins worth noting. Startup is faster — require and the loading of the standard library are optimised, so your app boots noticeably quicker, which matters for your test suite, your console, and anything that starts processes frequently (CI, deploys, serverless-style workloads). And passing a block to a method is significantly faster — block invocation is a hot path in idiomatic Ruby (every each, map, and reduce), so optimising it is a broad, free improvement across essentially all Ruby code. As with previous releases, you upgrade and your existing code runs a little faster for nothing.

The smaller additions

Rounding it out: Struct.new gains keyword_init: true so you can build a struct with named arguments (Point.new(x: 1, y: 2)) instead of relying on positional order; Dir.children and Dir.each_child list directory contents without the . and .. entries you always had to filter out; branch coverage is added to the coverage tooling, so you can measure not just which lines but which branches your tests exercise; and there is the usual round of deprecations and standard-library tidying. Top-level constant lookup (::SomeConstant falling through to the top level from inside a namespace) is removed as a deprecated behaviour, which can surprise an old codebase on upgrade — worth a grep.

Should you upgrade?

Yes, as ever — it is low-risk and broadly beneficial. The block-level rescue is the change you will feel daily; yield_self, Hash#slice, and the string/dir methods are small conveniences you will adopt naturally; and the faster boot and block-passing are free performance wins for any app, with the boot improvement especially welcome for test and CI cycles. The only upgrade caveat is the removed top-level constant lookup, easily checked.

The through-line of recent Ruby is steady and reassuring: each year, a clutch of sharp little ergonomic improvements that make the language nicer to write, plus quiet performance work that makes the code you already have run faster. 2.5 is a textbook example — no drama, just a language that keeps getting more pleasant and a little quicker every Christmas.