Ruby 3の静的解析機能のRBS、TypeProf、Steep、Sorbetの関係についてのノート

こんにちは、フルタイムRubyコミッタとして働いてる遠藤(@mametter)です。

Ruby 3 は「静的型解析」を備えることが目標の 1 つになっています。遠藤が開発してる TypeProf は Ruby 3 の静的型解析エコシステムの中の 1 ツールです。しかし Ruby 3 の静的解析というと、RBS、TypeProf、Steep、Sorbet などいろいろなツール名が出てきてよくわからない、という声を何回か聞いたので、かんたんにまとめておきます。

3 行まとめ

  • RBS:Ruby の型情報を扱う言語。Ruby 3 にバンドルされる。
  • TypeProf:型注釈のない Ruby コードを型解析するツール。Ruby 3 にバンドルされる。
  • Steep/Sorbet:Ruby で静的型付けのプログラミングができるツール。

詳しくはそれぞれ以下で解説します。

RBS とは

RBS は、Ruby 3 の型を扱うための基盤です。おおよそ、次の 4 種類のものからなります。

  • RBS 言語:Ruby プログラムの型情報を記述するための記法(拡張子 .rbs)
  • Ruby 組み込みライブラリの型情報:Ruby の組み込みクラス(ArrayStringなど)の型情報を書いた.rbsファイル群
  • RBS ライブラリ:.rbs ファイルのパースや解析などをするライブラリ
  • rbs コマンド:.rbs ファイルを扱うための便利コマンド

いろいろありますが、Ruby プログラマが直接意識するのは 1 つめの RBS 言語だけだと思います *1 。よって、単に RBS と言ったら「RBS 言語」、または「RBS 言語で書かれたソースコード」を指すと考えるのがよいと思います。

RBS 言語の例を示しておきます(core/string.rbs よりものすごく抜粋)。Ruby っぽいですが、Ruby ではない別の言語になっています。

class String
  def empty?: () -> bool
end

この記述は、Ruby の組み込みクラスである String クラスの型情報(RBS言語で書かれている)の抜粋で、empty? という無引数のメソッドを持っていて、bool を返すということを表しています。 RBS ライブラリを使うと、こういう .rbs ファイルをパースして抽象構文木を得ることができます。 rbs コマンドは、.rbs ファイルを読んでメソッドを検索するなどができます。

RBS はそれ単体で何かをするものではなく *2 、Ruby 3 の型情報を扱うツールが共通で使いたくなるものを集めた gem になっています。この gem は Ruby 3 に同梱されます。しかし基本的には型解析ツール向けの gem であり、普通の Ruby プログラマは RBS 言語を読み書きすることはあっても、RBS gem を直接使うことはあまりないと思います。

TypeProf とは

TypeProf は、型注釈のない Ruby コードを無理やり解析する静的型解析器です。Ruby 3 にバンドルされます。TypeProf は RBS 基盤を活用して作られています。

TypeProf の特徴はなんといっても、「型注釈を書かなくてもなんとなく型解析っぽいことができる」という性質に極振りして設計されているところです。 キーポイントは、メソッド呼び出しの情報を活用して解析するところです。 これにより、たとえばdef hello(user) ... endという型注釈が一切ないメソッドに対しても、hello(User.new)という呼び出しがあれば「メソッドhelloUserインスタンスを引数に取る」ということを推論します。

また、一部のクラスに RBS 言語で型情報を書いて TypeProf に与えることもできます。TypeProf はユーザが明示した型情報を無条件に信用するので、解析精度や解析速度が向上します。

TypeProf について詳しくは別の記事で解説しています。

techlife.cookpad.com

Steep/Sorbet とは

Steep は、Ruby の静的型検査器です。RBS を使って、伝統的な漸進的型付けによる型検査を行うことができます。 単に型エラーを検出できるだけではなく、LSP を実装しているので、エディタ上での型エラー表示、補完、ドキュメント表示なども実装されています。 現状で RBS を使って便利さを実感できるのは、Steep だけです。 このへんがわかりやすい記事にリンクしておきます。

qiita.com

Sorbet は、また別の Ruby の静的型検査器です。 こちらは RBS ではなく RBI という独自形式の型注釈を使います(RBS から RBI への変換器も開発されています)。 ものすごくざっくり言ってしまうと、できることは Steep とおおよそ同じです。 とはいえ、Stripe や Shopify という大企業ですでに数年ほど経験を積んでいるので、完成度はとても高いです。 解析器はC++で書かれていて、解析速度をものすごく重視しています。

Steep も Sorbet も Ruby 3 にバンドルされる予定はありません。 Ruby の設計者である matz が、「型注釈を書くことを Ruby本体として推進しない」と判断した結果です *3 。この判断と相性の良い TypeProf は将来の期待とともに Ruby 3 にバンドルされますが、型注釈を書くことをいとわない人は Steep や Sorbet を使うとよいと思います。

なお、Steep 自体はバンドルこそされませんが、RBS はもともと Steep の型注釈言語でした。 Ruby 組み込みライブラリや gem の型情報を各種ツール間で共通化したかったので、RBS という形で共通基盤として切り離され、Ruby本体に同梱されることになりました。

再度まとめ

  • RBS: Ruby 3 の型情報を扱う言語を始めとする基盤。Ruby 3 にバンドルされる。
  • TypeProf: 型注釈のない Ruby コードを型解析するツール。Ruby 3 にバンドルされる。現状の主機能は Ruby コードからの RBS スタブ生成。
  • Steep/Sorbet: Ruby の静的型検査器。型注釈を書く必要はあるが、Ruby で静的型の便利なプログラミング体験ができる。IDE での補完やドキュメント表示も。

*1:厳密に言うと、rbs コマンドは触ることもあるかも。

*2:厳密に言うと、単体でも動的型検査機能が使えます。

*3:これについてはいろいろな意見があると思いますが、個人的にはそういう言語も面白いと思っていて、そのために苦しみながら TypeProf を開発しています。

/* */ @import "/css/theme/report/report.css"; /* */ /* */ body{ background-image: url('https://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527163350.png'); background-repeat: repeat-x; background-color:transparent; background-attachment: scroll; background-position: left top;} /* */ body{ border-top: 3px solid orange; color: #3c3c3c; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; line-height: 1.8; font-size: 16px; } a { text-decoration: underline; color: #693e1c; } a:hover { color: #80400e; text-decoration: underline; } .entry-title a{ color: rgb(176, 108, 28); cursor: auto; display: inline; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; font-size: 30px; font-weight: bold; height: auto; line-height: 40.5px; text-decoration: underline solid rgb(176, 108, 28); width: auto; line-height: 1.35; } .date a { color: #9b8b6c; font-size: 14px; text-decoration: none; font-weight: normal; } .urllist-title-link { font-size: 14px; } /* Recent Entries */ .recent-entries a{ color: #693e1c; } .recent-entries a:visited { color: #4d2200; text-decoration: none; } .hatena-module-recent-entries li { padding-bottom: 8px; border-bottom-width: 0px; } /*Widget*/ .hatena-module-body li { list-style-type: circle; } .hatena-module-body a{ text-decoration: none; } .hatena-module-body a:hover{ text-decoration: underline; } /* Widget name */ .hatena-module-title, .hatena-module-title a{ color: #b06c1c; margin-top: 20px; margin-bottom: 7px; } /* work frame*/ #container { width: 970px; text-align: center; margin: 0 auto; background: transparent; padding: 0 30px; } #wrapper { float: left; overflow: hidden; width: 660px; } #box2 { width: 240px; float: right; font-size: 14px; word-wrap: break-word; } /*#blog-title-inner{*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-position: left 0px;*/ /*}*/ /*.header-image-only #blog-title-inner {*/ /*background-repeat: no-repeat;*/ /*position: relative;*/ /*height: 200px;*/ /*display: none;*/ /*}*/ /*#blog-title {*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-image: url('https://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527172848.png');*/ /*background-repeat: no-repeat;*/ /*background-position: left 0px;*/ /*}*/