2025/4/16-18に愛媛県松山市で開催された RubyKaigi 2025 にスマートバンクはHack Space Sponsorとして協賛し、登壇者2名を含む計11人で参加しました。今回は参加エンジニアによるセッションレポートをお送りします!
coyote
coyoteからはtk0miyaさんが発表された「Introducing Type Guard to Steep」についてレポートします。発表内容についてより詳しく知りたい方は、発表スライドが公開されていますので御覧ください。
SteepはRBSの型検査機構ですが、バージョン1.10でType Guard for Union Types(Union型に対するType Guard)がリリースされました!これは User | nil
のようなUnion型の変数に対しnilチェックなどを行うことで、その後の型をUser型に絞り込むType Narrowingを実現するためのものです。Type Guardとは、このType Narrowingに用いられる式のこと(この例の場合 if user
)を指します。
従来、ActiveSupportの #present?
メソッドのような、実行時に型を絞り込むメソッドはSteepでType Guardとして認識されませんでした。この課題を解決するために「Type Guard for Union Types(Union型に対するType Guard)」という機能が提案され、前述の通りSteepの1.10でリリースされました。これは、式のうち以下の条件を満たすものをType Guardとみなす変更です。
- メソッド呼び出しであること
- 呼び出しのレシーバーが変数または純粋なメソッド呼び出しであること
- レシーバーの型がUnion型であること
例えば User#present?: () -> true
と NilClass#present?: () -> false
のような型定義を行うことで、 if user.present?
というコードにおいて、ifブロック内ではuserがUser型に、elseブロック内ではnil型に絞り込まれるようになります。
発表の後半では、 User-defined Type Guard(ユーザー定義Type Guard)についてのお話がありました。これは、型定義の際に判定メソッドに # guard: <predicate>
という形式のアノテーションを追加することで、そのメソッドをType Guardとして定義するものです。この機能はまだ開発中であり、絞り込みの計算がうまくいかないケースなどもあるとのことでした。この機能がリリースされるとより柔軟に型を記述することが可能になるので待ち遠しいですね。
今回の発表を受けて、弊社内でもRBSによる型定義を頑張っていこうという機運が高まっています。Steepやsinsokuさんが発表されていた rbs-trace
を活用しつつ、より安全なコードを書いていきたいと考えています!
ohbarye
私からはsinsokuさんによる”Automatically generating types by running tests”についてレポートします。本発表はテスト実行時に型情報を収集し、自動的にRBSインラインコメントとして挿入するhttps://github.com/sinsoku/rbs-trace gemについてでした。
使い方はとても簡単で、gem追加後に RBS_TRACE=1 bundle exec rspec
のように実行するだけでRBSインラインコメントがガガガッと挿入されていきます。ただし型生成はテスト実行時に扱うデータに依存するためテストがなければ型宣言も生成されず、テスト品質が型宣言の品質に直結します。テストは大事ですね。
パフォーマンス面ではテスト実行時間が1.5〜5.4倍ほど遅くなったものの型宣言の生成は一度だけ行えばよいため実用上の問題は少ないというのも納得です。
本発表について個人的にすごい!と感じた点は以下です。
- 複数のテストケースで異なる型の引数が渡された場合は、自動的にunion typeとして宣言する
- トークを聞きながら「parallel testなどはどうするんだろう?」と思っていた点で、発表中に明確な回答が得られて良かったです。
- 実際のRedmineやMastodonといったreal worldなRailsアプリケーションで検証して型生成に成功している
- 生成された型の正しさを検証するのは難しいですが概ね目視で確認されたとのこと。パワー。
- メソッドの戻り値がvoidなのかどうかを判定するために呼び出し元のコードをPrismでパースして判定している
特に3つ目について、Prismパーサーによる呼び出し元コード構造の解析は、私の”On-the-fly Rewriting Method Deprecations”でも同様のアプローチを採用しており、異なる課題解決に同じテクニックが活用されている点が興味深かったです。
廊下にいた登壇後のsinsokuさんに声をかけた折、発表時間の制約で話せなかった裏話も聞くことができました。
- rbs-traceの実装リファクタリング時に盛大に壊れた事案があった
- 当初は型情報を単純なHashで管理していたが並列テスト対応(複数のRBS宣言のマージ)のためにRBS ASTを扱うアプローチに切り替えた
- 発表ではRuby ASTの話をしていたけど、実際にはRBS ASTも含めて2つのASTを扱っているのが面白ポイント
- RBS ASTのドキュメントはほとんどなく、RBSの実装コードを読み解く必要があった
などなど。30分の登壇のために削ぎ落としたものについて登壇者と話すのは楽しいですね、みなさんも話しかけていきましょう(話しかけてください〜)!
弊社スマートバンクも最近RBSインラインコメントを書くムーヴが起きつつあるのでガッとブートストラップするためにrbs-trace使ってみたいと思います。
nagasawa
nagasawaからはtagomorisさんによる「State of Namespace」についてレポートします。
このセッションでは、主にRubyKaigi 2024の時点からNamespaceでどのような開発を行っていたかについてのトークが行われました。この一年間はひたすらデバッグ作業をされていたそうで、処理系全体に影響のある機能開発ゆえの苦労が随所に現れていました。
例えば、デバッグ作業の過程で起きた問題の1つであるクラス定義に関する内容が面白かったです。Rubyのクラス定義は RClass
構造体と rb_classext_t
構造体を持つ RClass_and_rb_classext_t
によって定義されています。そして、Namespaceの機能は、このrb_classext_t を RClass 側で複数持てるよう変更を加え、Namespace毎にクラス定義を保持できるよう実装されているそうです。
ただ、このアプローチの初期実装では Ruby::VERSION
の値を再設定しているテストでSEGVが発生する問題を抱えていたそうです。原因としては、先ほど話にあったNamespace毎に持てるようにした rb_classext_t
はshallow-copyする形で実装されており、内部にある const_tbl
が別のNamespaceでunsetされた際にfreeされてしまっていた関係で参照時にSEGVに遭遇したようです。解決としては rb_classext_t
をdeep-copyすることで解消されたとのことでした。
この他にも、Namespace間のautoloadの問題やExtensions(.so)の読み込み問題などNamespaceのデバッグで出会ったいくつものケースについて詳細にトークが行われ、CRubyの様々な構造について知ることができる非常に面白いセッションでした。
セッションのスライドはすでに公開されているため、ご興味があるかはぜひ以下の資料をご覧ください!
State of Namespace - Speaker Deck
moznion
moznionからはmaximecbさんによる「ZJIT: Building a Next Generation Ruby JIT」の発表についてレポートいたします。みなさんお待ちかね、新しいRubyのJIT (Just-In-Time compiler) であるZJITの紹介セッションでした。
microJITから始まるRubyのJITの歴史を辿りつつ、今のRubyで利用されているYJITの現状と課題についてが論じられました。YJITは2021年の登場時から20%程度の性能改善に寄与しており、Rubyのバージョンが上がるごとにその性能は依然上がり続けており、現にRuby 3.5 devでも性能は向上していて大変素晴しい……一方で性能の伸びが対数的に鈍化してきたという課題も明らかになりました。こうした成長の鈍化はマイクロベンチマークでとりわけ顕著となってしまい、時折コミュニティの内外からの指摘があるようです。「良い言語であっても遅ければいずれ滅ぶ」という課題意識、そして「”Ruby”をボトルネックにしない」というモチベーションから生まれたのがこのZJITとのことでした。
ZJITはYJITと「地続きの後継」ではない新しいJITになっています *1。YJITはLBBV (Lazy Basic Block Versioning) によるJITであり、これはご存知の通り一定の成功を納めていますが、メンテナンスコストが高いという問題がありました。ZJITはこのLBBVではなく、いわゆる教科書的な良く知られているアプローチによるJITです。それにより、標準的であり実現上のリスクを低く取り組めるようになり、それに伴ってメンテナンス性 (保守性や新しいメンバーの開発への参加しやすさ) が高まるというベネフィットも得られています。パフォーマンスについてもYJITに肉薄しており、ワークロードによってはZJITがYJITを上回るケースもあるようです。
Matzのキーノートでも触れられていましたが、ZJITの「Rubyの今後20年を耐えうるJITを作る」というスローガンは非常に尊いなと思った次第でした。
なおZJITはupstreamに既にマージされており、数週間以内のうちに我々の手元でも試せるようになるとのことでした。そしてRuby 3.5でoptionalではあるものの利用可能になるとのこと! 楽しみですね。
ちなみにセッション中でも触れられていましたが、ZJITの読み方は「ゼット (ゼッド) ジット」じゃなくて「ズィージット」とのことです。注意して参りましょう。
参加エンジニア皆でセッションレポートを書こう!と書き始めたところ興味深いセッションばかりで分量が増えたため前後編に分けることにしました。後編もお楽しみに!
スマートバンクでは、一緒にRubyで開発する仲間を募集しています! smartbank.co.jp
*1:なおYJITの開発チームがそのままZJITの開発チームに移行しており、ここ数年のJITの知見を活かせているとのことです