Ruby 4.0 新機能と Ruby 3.x 系コードの互換性維持で注意すべき点

昨日 Ruby 4.0 がリリースされた。最近はもっぱら Python ばかり使っているが、かつては Ruby をよく使っていたこともあり、新機能及び、今後の Ruby スクリプト保守のため互換性維持のために気をつけなければならない点をまとめようと思う。本稿では、まず Ruby 4.0 の新機能を俯瞰し、そのうえで「Ruby 3.x を前提としたコードを書くもの」にとって移行時・保守時にどこを警戒すべきかを、出典を明記しつつ整理する。


Ruby 4.0 の新機能(俯瞰)

1. Ruby Box(Ruby::Box): 名前空間/定義の分離機構(実験的)

Ruby 4.0 は “Ruby Box” を導入する。環境変数 RUBY_BOX=1 で有効化され、Ruby::Box により「定義の分離(isolation)」を提供する実験的機能である。モンキーパッチやグローバル/クラス変数、クラス/モジュール定義、ロードした Ruby/ネイティブライブラリの影響を Box 単位に隔離できることが目的として説明されている。公式のリリース告知も “Ruby Box is a new (experimental) feature to provide separation about definitions” と明示している。

2. ZJIT: 新しい JIT(実験的)

Ruby 4.0 は “ZJIT” を導入する。公式(preview3 日本語)では「実験的なメソッドベースの JIT」と説明され、–zjit フラグで利用できること、ビルドに Rust 1.85.0 以降が必要なこと、Ruby 4.0.0 時点ではインタプリタより速いが YJIT ほど速くないこと、現時点では本番導入を避けるべきであること、Ruby 4.1 で改善して本番利用可能にする目標が述べられている。

3. Ractor の改善(Ractor::Port を中心とした API 再設計)

Ractor(並列実行機構)に API 再設計が入っている。互換性面では、Ractor::Port 導入に伴い複数の API が削除されている(詳細は後述)。

4. 言語仕様の変更(例: *nil / 行頭論理演算子)

Ruby 4.0 の言語仕様変更として、少なくとも以下が公式 NEWS に明記されている。

  • *nil は nil.to_a を呼び出さなくなった(**nil が nil.to_hash を呼び出さないのと同様)。
  • 行頭の論理二項演算子(||, &&, and, or)が前行からの継続として扱われるようになった(fluent dot と同様)。

参照

  • Ruby 4.0.0 Released(公式リリース告知): ruby-lang.org
  • NEWS for Ruby 4.0.0(Ruby 公式 NEWS): docs.ruby-lang.org
  • Ruby 4.0.0 preview3 リリース(言語仕様・ZJIT注意等の要約あり、日本語): ruby-lang.org(ja)
  • Fedora “Changes/Ruby 4.0”(互換性・stdlib・C API を要点化): fedoraproject.org
  • Reading Ruby 4.0 NEWS with Pros(NEWS を読み解く技術記事): dev.to
  • cgi library is removed from default gem(CGI 変更点の解説): zenn.dev
  • What Is New In Ruby 4.0(概観記事、補助参照): saeloun blog

Ruby 3.x 前提コードの移行・互換性維持で注意するべき点

以下は、新機能ではなく「Ruby 3.x 前提の既存コードが Ruby 4.0 で壊れやすい箇所」「保守観点で差分が効いてくる箇所」を中心にまとめる。実務上は言語機能よりも、標準添付ライブラリ、周辺ツール、非推奨→削除の移行が主戦場になりやすい。


1) 標準添付ライブラリ(stdlib / default gems)由来の互換性問題

CGI ライブラリが default gems から削除

Ruby 4.0 では CGI ライブラリが default gems から削除され、従来の require ‘cgi’ を前提とするコードは注意が必要になる。Fedora の互換性メモでは「CGI library is removed from the default gems」と明記され、提供されるのは cgi/escape の一部メソッド群のみとされている。

Zenn の解説記事では、Ruby 4.0 で require ‘cgi’ すると警告が出ること、CGI.new のようなフル機能利用がエラーになり得ること、今後は require ‘cgi/escape’ を用いること、フル機能が必要なら Gemfile に gem ‘cgi’ を明示する必要があることが、具体例込みで説明されている。

SortedSet が自動ロードされなくなる(set/sorted_set.rb の削除)

Set が stdlib からコア側へ移る流れの中で、set/sorted_set.rb が削除され、SortedSet は autoload されない定数になった。Fedora のメモは、SortedSet を使うなら sorted_set gem をインストールし require ‘sorted_set’ が必要だと明記している。


2) Ractor 利用コードは API 削除が直撃する

Ractor::Port 導入に伴い、以下の API が削除されている(互換性メモで明示)。Ractor を並列処理の基盤として使っているコードは、これらの呼び出しが存在しないかをまず確認する必要がある。

  • Ractor.yield
  • Ractor#take
  • Ractor#close_incoming
  • Ractor#close_outgoging(原文表記)

3) 以前から非推奨だったものが「削除」へ進む

Process::Status の演算子削除

Process::Status#& と Process::Status#>> が削除された。dev.to の NEWS 読み解き記事では、これらは Ruby 3.3 で deprecated になっていたもので、ステータスコードを覗く用途に使われていたが、専用メソッドがあるため不要になった、と説明されている。

ObjectSpace._id2ref が deprecated

ObjectSpace._id2ref は deprecated になった。dev.to の記事では、object_id からオブジェクトを取得するこの API を deprecated にする判断自体は以前からあったが、いろいろな経緯で忘れられており、今回ついに deprecated になった、という説明がある。


4) 例外・バックトレースの見え方が変わる(テストの文字列一致に注意)

バックトレースの表示が変わる。dev.to の記事では、wrong number of arguments の ArgumentError において、バックトレースにレシーバのクラス/モジュール名が含まれるようになった例(Ruby 3.4 と Ruby 4.0 の差分)が示されている。例外メッセージやバックトレース文字列を厳密一致で検証しているテスト(スナップショット的な運用を含む)は、ここで落ちやすい。


5) C 拡張(ネイティブ拡張)を持つ場合の注意

C API 側も更新がある。Fedora のメモでは、IO に関して rb_thread_fd_close が deprecated で no-op になったこと、ファイルディスクリプタを Ruby に公開するなら RUBY_IO_MODE_EXTERNAL を使って IO インスタンスを作り、rb_io_close(io) で閉じるべきこと、fd を直接 close すると pending operation を interrupt できず undefined behavior に繋がりうることが記載されている。


6) RubyGems / Bundler(周辺ツール)の追随

実務上、Ruby 本体よりも RubyGems / Bundler の更新で CI や運用の前提が崩れることがある。Ruby 4.0 への移行では、言語仕様の差分だけでなく、gem の解決・インストール・ビルドの流れを含めた手順全体を Ruby 4.0 環境で通す必要がある。特に、RubyGems / Bundler の breaking changes により、古いコマンド/オプションや前提が削除されている場合がある。


移行の実務チェックリスト(短縮版)

本稿の内容をそのまま作業に落とすための最低限のチェックリストをまとめる。

  1. require ‘cgi’ や CGI. を検索し、フル機能が必要なら Gem 依存として gem ‘cgi’ を明示する。escape 系のみなら require ‘cgi/escape’ へ寄せる。参照: Fedora / Zenn。
  2. SortedSet を検索し、必要なら sorted_set gem を追加して require ‘sorted_set’ に切り替える。参照: Fedora。
  3. Ractor.yield / Ractor#take / Ractor#close_incoming / Ractor#close_outgoing を検索し、Ractor::Port 前提の設計へ移行する。参照: Fedora。
  4. Process::Status#& / #>> を検索し、exitstatus/success? 等の専用 API に寄せる。参照: dev.to。
  5. ObjectSpace._id2ref に依存している箇所がないか確認し、設計を見直す(deprecated)。参照: dev.to / Fedora。
  6. 例外メッセージ/バックトレースを文字列で厳密一致しているテストがあれば、Ruby 4.0 の表示変更で落ちる可能性を前提に見直す。参照: dev.to。
  7. C 拡張(ネイティブ拡張)を使う gem/自前拡張がある場合は、Ruby 4.0 で bundle install からテストまで通し、rb_thread_fd_close の no-op 化などの影響がないか確認する。参照: Fedora。

付録: コード断片(必要最小限)

本稿ではブログ本文の可読性を優先し、コード断片は必要最小限に留める。

1
2
3
4
5
# Ruby 4.0: 行頭論理演算子(&& など)が前行の継続として扱われる
if condition1
  && condition2
    do_something
end
1
2
3
# CGI: escape 系だけを使うなら require "cgi/escape"
# フル機能を使うなら gem "cgi" を明示して require "cgi"
require "cgi/escape"

結び

Ruby 4.0 は “Ruby Box” と “ZJIT” の導入を掲げつつ、既存コード側には stdlib/default gems の整理(CGI、SortedSet など)や、並列機構(Ractor)の API 再設計、deprecated だった API の整理、バックトレース表示の変更、C API の注意点など、保守観点で無視できない互換性の差分が含まれている。移行時は「新機能を使うか」より先に、「既存コードが暗黙に依存していた前提が残っているか」を洗い出すことが重要になる。

最終的には、Ruby 4.0 環境で bundle install からテスト完走までを確実に通し、問題が出た箇所を「stdlib/default gems」「Ractor」「deprecated→削除」「表示変更」「C API」「周辺ツール」のどこに属するかで切り分けるのが、最も短い。