Preparing for Ruby 4.0: Ten Gems Moving from Default Standard Library to Bundled
Over time, Ruby’s standard library grew from 1.0’s modest origins to 3.x’s much more comprehensive scope — adding features across versions, adding gem after gem into the default distribution.
Ruby 4.0 addresses this by moving ten mature gems from “default” status to “bundled” status.
These gems remain available. Bundler loads them automatically when your Gemfile lists them, or when a dependency requires them in their gemspec.
The change is narrower than it first appears — but that means that you might not notice the change even after upgrading to Ruby 4.0, since it does not always present itself. This might lead to some confusion if you encounter it in a context where it can crop up – which we will discuss next.
What “Core” vs “Default” vs. “Bundled” Actually Means
Before we get into the specific gems, it’s worth clarifying the distinction, because it’s genuinely easy to misread.
Ruby has a core library. This includes classes like Array and String – classes that are always available and do not require a “require” statement to use.
Beyond that, Ruby itself ships with gems – two kinds, in fact: default gems and bundled gems. Default gems are always available, with or without Bundler being setup, and they do not require a Gemfile entry — require 'date' works anywhere Ruby runs, no questions asked.
Bundled gems also ship with Ruby, but aren’t loaded unless Bundler includes them, either because your Gemfile lists them or because another dependency pulls them in.
These might seem very similar – and, to be sure, they are, but they are some important distinctions.
One is responsibility; default gems are maintained by the core Rails team, whereas bundled gems are maintained by outside authors.
Another important area is removability: you can uninstall bundled gems, but you cannot uninstall default gems.
Interestingly, this distinction does NOT extend to upgrading gems; you can upgrade both bundled and installed gems, though the core library remains locked to its source version of Ruby.
Regardless, you can strip the default gems to save a modest amount on Docker image size; here’s the result I got when running some tests for this article:
| Ruby | Default Gems | Bundled Gems | Image w/ Gems | Image w/o Bundled |
|---|---|---|---|---|
| 3.0 | 80 | 8 | 161MB | 154MB |
| 3.1 | 72 | 15 | 140MB | 124MB |
| 3.2 | 73 | 15 | 144MB | 129MB |
| 3.3 | 72 | 16 | 147MB | 130MB |
| 3.4 | 60 | 29 | 155MB | 132MB |
| 4.0 | 48 | 39 | 171MB | 134MB |
The above gains are modest – 37mb for Ruby 4.0 – but in some context, particularly in large cloud computing scenarios, that 37mb can add up fast.
Of course, these benefits only accrue if you actually audit and update your Gemfile rather than adding all ten gems back reflexively.
Affected Gems
As we saw from the above chart, there’s a jump of ten bundled gems from Ruby 3.4.
Ruby 3.3 introduced deprecation warnings for direct require calls on several of these gems. Those warnings were the signal that 4.0 was coming; see the Ruby 3.3.0 release notes for the early announcements. In theory, this should mean that there was ample time to adjust and no one faced any difficulties – but, as you know, warnings are easy to ignore, and are therefore often ignored.
The complete list — with exact versions — is at stdgems.org for Ruby 4.0.2. We summarize the key ones here, with migration notes for each.
| Gem | Version | Common Uses | Rails Impact / Migration Notes |
|---|---|---|---|
| benchmark | 0.5.0 | Micro-benchmarks, perf testing | Consider switching to benchmark-ips. |
| fiddle | 1.1.8 | C extensions, FFI bindings | Niche for custom FFI; grep for direct require 'fiddle' — rare in web apps. Add explicitly if found. |
| irb | 1.16.0 | Interactive REPL, debugging | Rails console uses debug gem (faster, with stack traces); add gem 'irb' for standalone or custom .irbrc in dev/test groups. |
| logger | 1.7.0 | App logging | Rails uses ActiveSupport::Logger wrapper; add for standalone cron or Rake. Consider semantic_logger for JSON/context. |
| ostruct | 0.6.3 | Mutable hash-like objects | Migrate to core Data.define (~51% less memory, 2.7x faster instantiation). Retain ostruct only if mutation is essential. |
| pstore | 0.2.1 | Transactional file DB | Legacy for simple persistence; migrate to SQLite, JSON files, or ActiveRecord in Rails. |
| rdoc | 7.0.3 | Documentation generator | Legacy tool; migrate to YARD or Solargraph for modern docs and LSP support. |
| readline | 0.0.4 | Console editing (C extension) | Often system-provided; add if IRB or pry input breaks on your platform — test consoles first. |
| reline | 0.6.3 | Pure-Ruby readline | MRI IRB companion; add to dev group for pure-Ruby consoles. |
| win32ole | 1.9.2 | Windows COM automation | Windows-only; use platforms :mswin conditional in Gemfile — skip for Linux and Docker environments. |
Lets discuss few notes on gems.
First, the Benchmark class has been moved to a bundled gem; this is quite frequently used, so its worth noting. However, as noted, its quite frequently used, so you may already have it loaded via another dependency – meaning that it may randomly fail later if that dependency is changed. Therefore, if your code contains “Benchmark.measure” or similar, be sure to use add it to your Gemfile. Note, though, that many people – myself included – consider benchmark-ips superior: it includes more functionality, such as reporting the standard deviation, and includes features specifically designed for comparing very quick-executing pieces of code.
The win32ole gem is a Windows-only gem used for OLE/COM services; if you don’t know what that is, you almost certainly don’t need it. If you do need it, you have my sympathies.
Pstore is a simple and – at this point – infrequently used file-based DB. If you’re using this, migrate to anything-that-isn’t-Pstore.
Fiddle is used for FFI bindings; most Ruby developers use FFI bindings only indirectly, though if you’re code does use it, be sure to add it to your Gemfile. Its also worth noting that Fiddle isn’t the only FFI binding for Ruby, or even necessarily the best one: Ruby-FFI is also a strong contender.
Reline is an interesting gem; its a pure-ruby version of readline, with no dependency on libreadline, though as readline isn’t exactly an uncommon library for a modern system to have, you likely don’t need it – and in any event, this is typically only used in dev environments.
One significant move, though, is that of the OpenStruct gem, which, although handy, has some serious inherent flaws.
OpenStruct
OpenStruct builds mutable, hash-like objects at runtime — a useful pattern for ad-hoc configuration structs or test fixtures. Its flexibility comes at a cost, though: memory usage is notably higher than alternatives, and instantiation is slower.
There’s also a cost to debugging and productivity: the ability to silently define new struct members at any time introduces an entirely new class of bugs.
There’s an alternative, though: Ruby 3.2 introduced Data.define, an immutable struct primitive that supports pattern matching and costs significantly less to instantiate. For most OpenStruct use cases.
One wrinkle worth noting: Data.define returns a new class each time it’s called, so for benchmarking purposes you should define the class once rather than inside the measurement block. Redefining the class on each iteration would measure class creation, not object instantiation — which is not what we’re comparing here.
Benchmark with this script (bench.rb):
require 'benchmark/ips'
Config = Data.define(:host, :port)
Benchmark.ips do |x|
x.report('OpenStruct') do
OpenStruct.new(host: 'localhost', port: 3000)
end
x.report('Data.define') do
Config.new(host: 'localhost', port: 3000)
end
end
Pre-migration (add gems first):
$ bundle add ostruct benchmark-ips
$ bundle exec ruby bench.rb
OpenStruct 370.0 i/1000ms
Data.define 1000.0 i/1000ms (2.7x faster)
The syntax is not terribly dissimilar: you can migrate OpenStruct.new(key: value) to Data.define(:key).new(key: value). However, if you rely on the ability of OpenStruct to dynamically add keys, you’ll likely need to
Conclusion
Ruby 4.0’s slimmer core can pay dividends in CI and Docker environments, though it requires some adjustment of your Dockerfile; although not a cure-all, explicit dependencies like this default-to-bundled shift help future-proof your applications against further changes. For the complete, authoritative list of gem status changes, stdgems.org for Ruby 4.0.2 is the definitive reference.
Further Reading
Ruby 4.0.2 Standard Gems
stdgems.orgDefault vs. bundled gems list
Ruby 4.0.2 Release Notes
Ruby-lang.orgOfficial stdlib changes announcement
Ruby 3.3.0 Release Notes
Ruby-lang.orgDeprecation warnings prelude to 4.0
Data Class
Ruby DocsImmutable ostruct alternative
benchmark-ips
GitHubStatistical benchmarking
You May Also Like
How to Generate a Gemfile.next.lock for Faster Rails Upgrades
A step-by-step technical guide to generating and managing a Gemfile.next.lock file using the bootboot plugin for smoother Rails upgrades.
Securing Your Gemfile: How to Use Bundler Checksums to Prevent Supply Chain Attacks
A guide to using Bundler checksums to secure your Gemfile and prevent supply chain attacks in your Ruby on Rails application.
A Guide to Upgrading Gems and Dependencies in Ruby
Learn how to manage Gemfile updates safely.