Rubyで型チェック！動かして理解するRBS入門 〜サンプルコードでわかる！Ruby 3.0の主な新機能と変更点 Part 1〜

## はじめに

Ruby 3.0ではRubyのコードに型定義情報を提供するRBSという仕組みが導入されます。
この記事では簡単なサンプルプログラムを通して、RBSとその周辺ツールの使い方や役割を説明します。
なお、説明する内容はあくまで初歩的な内容です。予めご了承ください。

### 動作確認時の実行環境

本記事の執筆時点ではまだRuby 3.0は正式にリリースされていません。
正式リリース時、または今後のバージョンアップによってこの記事の内容と実際の挙動が異なる可能性もあります。

本記事の執筆時に使用した実行環境は以下のとおりです。

- Ruby 3.0.0dev (2020-11-13T16:46:08Z master 7826210541) [x86_64-darwin19]
- rbs 0.17.0
- typeprof 0.4.2
- steep 0.36.0

ただし、型チェックに関わる機能はgemとして提供されているため、gemをインストールすればRuby 2.7でも動かすことができます。詳しくは後述します。

### サンプルコード

本記事で使用したサンプルコードは以下のリポジトリに置いています。

https://github.com/JunichiIto/ruby-3-0-sandbox/tree/main/rbs-sandbox

### 本記事の位置づけ

この記事は[Ruby 3\.0 Advent Calendar 2020](https://qiita.com/advent-calendar/2020/ruby3) 7日目の記事です。

また、Ruby 2.3のリリース時から毎年続けている、「サンプルコードでわかる！Ruby 2.xの主な新機能と変更点」シリーズのRuby 3.0バージョンでもあります。（RBS以外の新機能をまとめた記事も後日公開する予定です）

- [サンプルコードでわかる！Ruby 2\.3の主な新機能 \- Qiita](https://qiita.com/jnchito/items/0faac073cb77417d61c7)
- [サンプルコードでわかる！Ruby 2\.4の新機能と変更点 \- Qiita](https://qiita.com/jnchito/items/9f9d45581816f121af07)
- [サンプルコードでわかる！Ruby 2\.5の主な新機能と変更点 \- Qiita](https://qiita.com/jnchito/items/f182b6f0093a6a3701a1)
- [サンプルコードでわかる！Ruby 2\.6の主な新機能と変更点 \- Qiita](https://qiita.com/jnchito/items/ce11cb7fccdf4b66af24)
- [サンプルコードでわかる！Ruby 2\.7の主な新機能と変更点 \- Qiita](https://qiita.com/jnchito/items/ff89392f46cc9d500c94)

**Part 2も書きました！**
Ruby 3.0のその他の新機能と変更点は以下の記事にまとめました。
こちらもあわせてどうぞ。

[サンプルコードでわかる！Ruby 3\.0の主な新機能と変更点 Part 2 \- 新機能と変更点の総まとめ](https://zenn.dev/jnchito/articles/24e0bd7fd1045d)

それでは以下が本編です。

## 自分で作ったコードに型定義を付ける

自分で簡単なRubyのクラスを作り、そのクラスに型情報を定義してRubyの型チェックがどのように実現されるのかを見ていきましょう。
以下の手順に従えば、みなさんもご自身でRuby 3.0の型チェックを体験することができます。

まず、適当なディレクトリを作って、そこに移動します。
ここでは `rbs-sandbox` というディレクトリを作りました。

```
$ mkdir rbs-sandbox
$ cd rbs-sandbox
```

`lib`ディレクトリを作り、その中に`fizz_buzz.rb`を作ります。

```
$ mkdir lib
$ touch lib/fizz_buzz.rb
```

`lib/fizz_buzz.rb`には以下のようなコードを書くことにします。

```ruby
class FizzBuzz
  def self.run(n)
    1.upto(n).map do |n|
      if n % 15 == 0
        'FizzBuzz'
      elsif n % 3 == 0
        'Fizz'
      elsif n % 5 == 0
        'Buzz'
      else
        n.to_s
      end
    end
  end
end
```

さて、この`FizzBuzz.run`メソッドに対して、自力で型定義を書いてもいいのですが、ここではRuby 3.0に付属する`typeprof`コマンドを使って型定義を自動生成してみます。

`typeprof`で型定義を自動生成するためには、目的のコードを実行するRubyコードが必要になります。
そこで、`runner/fizz_buzz_runner.rb`に次のようなコードを書いてみましょう。（注：`runner`というディレクトリ名は筆者が適当に付けた名前です。RBSの予約語とかではありません）

```
$ mkdir runner
$ touch runner/fizz_buzz_runner.rb
```

`runner/fizz_buzz_runner.rb`には次のようなコードを書きます。

```ruby
require_relative '../lib/fizz_buzz'

results = FizzBuzz.run(15)
puts results
```

念のため、上のコードが動作することも確認しましょう。以下のような実行結果が得られればOKです。

```
$ ruby runner/fizz_buzz_runner.rb
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
```

準備ができたので、`typeprof`を実行しましょう。
以下のコマンドを入力します。すると以下のような出力結果が得られます。
（注：本記事執筆時点では`unknown RBS type: RBS::Types::Bases::Top`という謎の警告も大量に出力されます）

```
$ typeprof runner/fizz_buzz_runner.rb
# Classes
class FizzBuzz
  def self.run : (Integer) -> Array[String]
end
```

この出力結果が、`typeprof`によって自動的に型推論されたFizzBuzzクラスの型情報です。
`(Integer) -> Array[String]`とあるように、Integer型の引数を渡すと、Stringを要素に含むArrayが戻り値として返ってくることが表現されています。

次に、[Steep](https://github.com/soutaro/steep)というgemを使って、プログラムの型チェックを行ってみましょう。
SteepはRuby 3.0には付属しないため、別途インストールする必要があります。

```
$ gem install steep
```

gemをインストールしたら、`steep init`コマンドを実行します。

```
$ steep init
Writing Steepfile...
```

すると、次のような`Steepfile`が作成されます。

```ruby
# target :lib do
#   signature "sig"
#
#   check "lib"                       # Directory name
#   check "Gemfile"                   # File name
#   check "app/models/**/*.rb"        # Glob
#   # ignore "lib/templates/*.rb"
#
#   # library "pathname", "set"       # Standard libraries
#   # library "strong_json"           # Gems
# end

# target :spec do
#   signature "sig", "sig-private"
#
#   check "spec"
#
#   # library "pathname", "set"       # Standard libraries
#   # library "rspec"
# end
```

`Steepfile`はSteepを実行する際に必要な設定ファイルです。
設定例がコメントアウトされていますが、ここでは`Steepfile`の中身を以下のように書き換えることにします。

```ruby
target :lib do
  check "lib"
  check "runner"
  signature "sig"
end
```

上の設定では、`lib`ディレクトリと`runner`ディレクトリを型チェックの対象とし、型定義ファイルを`sig`ディレクトリに配置するという意味になっています。（注：本記事執筆時点では`Steepfile`のDSLに関する説明が見つけられなかったので、設定の意味については筆者の推測で書いています）

型定義ファイルを`sig`ディレクトリに配置しましょう。
次のように`sig`ディレクトリを作り、そこに`fizz_buzz.rbs`ファイルを作成します。

```
$ mkdir sig
$ touch sig/fizz_buzz.rbs
```

`sig/fizz_buzz.rbs`に先ほど`typeprof`で出力された型定義情報を書き込みます。

```rbs
class FizzBuzz
  def self.run : (Integer) -> Array[String]
end
```

これでSteepを使う準備ができました。では、`steep check`とコマンドを入力してください。

```
$ steep check
```

何か起きましたか？何も起きませんね。
何も起きないということは型チェックに成功したことを意味しています。

ちゃんと不正な型を検出できるかどうかを確認するために、`fizz_buzz_runner.rb`を次のように書き換えてみてください。

```diff
 require_relative '../lib/fizz_buzz'

-results = FizzBuzz.run(15)
+results = FizzBuzz.run('abc')
 puts results
```

こうすると、型チェック時にエラーが発生するはずです（引数にIntegerではなくStringを渡したため）。

```
$ steep check
runner/fizz_buzz_runner.rb:3:23: ArgumentTypeMismatch: receiver=singleton(::FizzBuzz), expected=::Integer, actual=::String ('abc')
```

エラーが出力されたことを確認したら、`fizz_buzz_runner.rb`のコードは元に戻してください。

Rubyの組み込みライブラリも型チェックの対象になります。たとえば、`fizz_buzz.rb`で以下のようにわざと文字列に対して`upto`メソッドを呼び出してみましょう。

```diff
 class FizzBuzz
   def self.run(n)
-    1.upto(n).map do |n|
+    '1'.upto(n).map do |n|
       if n % 15 == 0
```

これもやはり型チェックに引っかかります。（注：本記事執筆時点ではなぜか同じエラーメッセージが2回出力されます）

```
$ steep check
lib/fizz_buzz.rb:3:4: UnresolvedOverloading: receiver=::String, method_name=upto, method_types=(::string, ?::boolish) -> ::Enumerator[::String, ::String] | (::string, ?::boolish) { (::String) -> void } -> ::String ('1'.upto(n))
```

エラーが出力されたことを確認したら、`fizz_buzz.rb`のコードは元に戻してください。

Ruby 3.0の型チェックの基本説明は以上になります。

### ここまでの説明でわかること

ここまでの説明でわかることを以下にまとめます。

- Rubyの型情報はrbsと呼ばれるファイルに定義する（Rubyスクリプト本体に型情報は書かない）
- rbsは自力で書いてもよいが、`typeprof`を使うと型推論でrbsを自動生成できる（ただし目的のコードを動かすコードが必要）
- 型チェックの実行はRuby本体の機能でなく、外部のgemを利用する。Steepはそのうちのひとつ
- Rubyの組み込みライブラリは最初から型情報が提供されるので型チェックの対象となる

続いてもうちょっと応用的な使い方を見ていきます。

## 標準ライブラリの型チェックをする

この記事ではStringやIntegerのように`require`なしで使えるクラスを「組み込みライブラリ」（コアライブラリともいう）、DateクラスやPathnameクラスのように`require`が必要なクラスを「標準ライブラリ」と呼ぶことにします。

この項では標準ライブラリの型チェックを利用する手順を説明します。

`fizz_buzz_runner.rb`を次のように修正してみましょう。
ここでは15回という固定の回数ではなく、今日の日付の日にちぶん（つまり1〜31回）FizzBuzzを実行します。

```diff
+require 'date'
 require_relative '../lib/fizz_buzz'

-results = FizzBuzz.run(15)
+results = FizzBuzz.run(Date.today.day)
 puts results
```

この状態で`steep check`を実行します。

```
$ steep check
```

コードに問題がないので何も起きませんね。
ではわざと`.day`を`.dy`に変えてみましょう。

```diff
 require 'date'
 require_relative '../lib/fizz_buzz'

-results = FizzBuzz.run(Date.today.day)
+results = FizzBuzz.run(Date.today.dy)
 puts results
```

`.dy`は存在しないメソッドなので、`steep check`で怒られるはずです。どうでしょうか？

```
$ steep check
```

あれ？またまた何も起きませんでした……。
実は標準ライブラリを型チェックの対象にするためには、`Steepfile`にそのライブラリ名を指定する必要があります。
というわけで、`Steepfile`に以下の一行を追加します。

```diff
 target :lib do
   check "lib"
   check "runner"
   signature "sig"
+  library "date"
 end
```

こうすると`steep check`で型エラーを検知できます。

```
$ steep check
runner/fizz_buzz_runner.rb:4:23: NoMethodError: type=::Date, method=dy (Date.today.dy)
```

## gemの型チェックをする

Ruby 3.0リリース時にはRubyの組み込みライブラリや標準ライブラリについては、最初から型定義情報が同梱されます。
しかし、Ruby標準ではないサードパーティー製のgemについては、別途型定義が必要になります。

TypeScriptの[DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped)と同じように、RBSには[gem_rbs](https://github.com/ruby/gem_rbs)というリポジトリがあります。

https://github.com/ruby/gem_rbs

本記事執筆時点では、listen、rainbow、redis、retryableという限られた種類のgemしか型情報が登録されていませんが、今後RBSが普及するにつれて、型情報が増えていくことが期待されます。（もちろんみなさん自身がコントリビュートすることも可能です！）

ここでは試しに[Retryable gem](https://github.com/nfedyashev/retryable)の型定義ファイルを使ってみましょう。
まず、Retryable gemをインストールします。

```
$ gem install retryable
```

次に`fizz_buzz_runner.rb`を次のように書き換えます。（Retryableの型チェックをしたいだけなので、コード自体にあまり意味はありません）

```ruby
require 'date'
require 'retryable'
require_relative '../lib/fizz_buzz'

Retryable.retryable(tries: 3) do
  results = FizzBuzz.run(Date.today.day)
  puts results
end
```

さらに`git submodule`を使って、gem_rbsをインポートします。

```
# git initしてなかったら、まずgit initを実行
$ git init

# git submoduleコマンドでgem_rbsをインポート
$ git submodule add https://github.com/ruby/gem_rbs.git vendor/rbs/gem_rbs
```

`Steepfile`を開いて、Retryableの型情報を読み込むようにします。
なお、`library "forwardable"`も追加しているのは、Retryableが内部的にForwardableモジュールを使っているため、この記述がないと`steep check`時に`UnknownTypeNameError: name=Forwardable`というエラーが出るためです。

```diff
 target :lib do
   check "lib"
   check "runner"
   signature "sig"
+  repo_path "vendor/rbs/gem_rbs/gems"
+  library "retryable"
+  library "forwardable"
 end
```

この状態で`steep check`を実行すると、型チェックに成功＝つまり何も起きないはずです。

```
$ steep check
```

ためしに`.retryable`を`.retryablee`に書き換えて見ましょう。

```diff
 require 'date'
 require 'retryable'
 require_relative '../lib/fizz_buzz'

-Retryable.retryable(tries: 3) do
+Retryable.retryablee(tries: 3) do
   results = FizzBuzz.run(Date.today.day)
   puts results
 end
```

こうすると型チェックに引っかかるはずです。

```
$ steep check
runner/fizz_buzz_runner.rb:5:0: NoMethodError: type=singleton(::Retryable), method=retryablee (Retryable.retryablee(tries: 3) do)
```

キーワード引数も型チェックされます。`3`を文字列の`"3"`に変えてみましょう。

```diff
 require 'date'
 require 'retryable'
 require_relative '../lib/fizz_buzz'

-Retryable.retryable(tries: 3) do
+Retryable.retryable(tries: "3") do
   results = FizzBuzz.run(Date.today.day)
   puts results
 end
```

`steep check`を実行すると、キーワード引数の型が違うと怒られます。

```
$ steep check
runner/fizz_buzz_runner.rb:5:20: ArgumentTypeMismatch: receiver=singleton(::Retryable), expected=::Retryable::Configuration::options, actual={ :tries => ::String } (tries: "3")
```

## VS Code用のエクステンションで型チェックする

SteepはVS Code用のエクステンションも提供しています。

https://github.com/soutaro/steep-vscode

VS Codeで"Steep"という名前のエクステンションを検索すると、以下のエクステンションが見つかるはずです。
まずこれをインストールしましょう。

![Screen Shot 2020-12-06 at 4.48.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7465/b0e0ee69-980c-e860-0d67-b626f0561221.png)

このエクステンションを使うためにはBundlerをセットアップする必要があります。
というわけで、`bundle init`を実行して`Gemfile`を作成します。

```
$ bundle init
```

`bundle init`が終わったら`Gemfile`を次のように編集し、`bundle install`を実行してください。

```
# frozen_string_literal: true

source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

gem "steep"
gem "retryable"
```

Bundlerのセットアップが終わったら、VS Codeで`rbs-sandbox`ディレクトリを開いてみましょう。
コードが1つ前の節で使った`(tries: "3")`の状態になっていれば、以下のように型エラーが通知されているはずです。

![Screen Shot 2020-12-06 at 4.43.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7465/72fc8f49-8750-0c9b-c76b-4e7a4a7d9946.png)

メソッド名のオートコンプリート機能も働きます（同時に型情報も表示されています）。

![Screen Shot 2020-12-06 at 4.50.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7465/153c7ee9-c66d-ecb0-63fc-63433cb7f2e4.png)

![I1Sil38gCO.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7465/22f22922-b27c-31f9-34a1-a8f8651f6239.gif)
カーソルをメソッドの上にhoverすると、そのメソッドの型情報も表示されます。

![Screen Shot 2020-12-06 at 4.47.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7465/6ecdefe6-ff41-581d-720c-4dfee2d91ca0.png)

### steep-vscodeを使った感想

静的型付け言語を使ったときのような開発体験をRubyでできるのは、なかなか新鮮でした！

ただ、筆者はRubyMineユーザーなので、個人的にはRubyMineでも早く同じような機能が提供されることを期待しています。
（[こちらのページ](https://www.jetbrains.com/ja-jp/ruby/whatsnew/)によると、「コーディング支援などの機能や RBS のサポートは、Ruby 3.0 のリリースバージョンが公開された後に実装される見込み」だそうです）

## typeprofと既存のrbsファイルを使って型情報の差分を取得する

すでに説明した通り、`typeprof`を利用すると型情報を自動生成することができます。
ただし、`typeprof`はコードから型を推測しているので、推測がうまくいかないときはコードの書き手が意図した通りの型情報が出力されない場合があります。

たとえば以下のようなコードがあったとします。

```ruby
# lib/person.rb
class Person
  def initialize(name)
    @name = name
  end
end
```

```ruby
# runner/person_runner.rb
require_relative '../lib/person'

Person.new('Alice')
```

この場合、`typeprof`の実行結果は以下のようになります。

```
$ typeprof runner/person_runner.rb
# Classes
class Person
  @name : String
  def initialize : (String) -> String
end
```

`initialize`の戻り値がStringになっていますが、コードの書き手の意図としては「インスタンス変数に名前をセットしただけ」であり、それを戻り値として返そうとしたわけではありません。

こういう場合は`typeprof`の出力結果を自分で編集する必要があります。そこで、`sig/person.rbs`は次のように戻り値をStringからvoidに変えて保存しました。

```
class Person
  @name : String
  def initialize : (String) -> void
end
```

続けて、Personクラスに`hello`メソッドを実装します。

```diff
 class Person
   def initialize(name)
     @name = name
   end
 
+  def hello
+    puts "Hello, I'm #{@name}!"
+  end
 end
```

`person_runner.rb`でも`hello`メソッドを呼び出すようにします。

```ruby
require_relative '../lib/person'

person = Person.new('Alice')
person.hello
```

しかし、この状態で`typeprof`を再実行すると、先ほど修正した`initialize`メソッドの戻り値がまたStringに戻ってしまいます。

```
$ typeprof runner/person_runner.rb
# Classes
class Person
  @name : String
  def initialize : (String) -> String
  def hello : -> nil
end
```

`typeprof`を実行するたびに以前修正した内容を何度も修正するのは面倒です。
この問題を避けるため、`typeprof`では実行用のコードだけでなく、既存のrbsファイルも型推論のインプットとして与えることができます。

というわけで、`typeprof`の引数に先ほど作った`person.rbs`を指定して実行してみましょう。
こうすると、既存のrbsファイルに定義されていない型情報だけが出力されます（既存の型情報はコメントアウトされて出力されます）。

```
$ typeprof sig/person.rbs runner/person_runner.rb
# Classes
class Person
# @name : String
# def initialize : (String) -> void
  def hello : -> nil
end
```

あとは差分として出力された`def hello : -> nil`を`sig/person.rbs`に追加すればOKです（ただし、ここも戻り値は`nil`ではなく`void`に変えた方がいいかもしれません）。

なお、`typeprof`は`-o`オプションを付けることで、自動生成された型情報をファイルに出力することもできます。

```
$ typeprof sig/person.rbs runner/person_runner.rb -o sig/person.gen.rbs
```

## rbsコマンドで型定義ファイルの雛形を作成する

ここまで`typeprof`コマンドを使って型定義ファイルを作成してきましたが、`typeprof`の代わりに`rbs`コマンドを使って型定義ファイルの雛形（プロトタイプ）を生成することができます。
以下は`rbs`コマンドで`fizz_buzz.rb`の型定義情報を生成する例です。

```
$ rbs prototype rb lib/fizz_buzz.rb
class FizzBuzz
  def self.run: (untyped n) -> untyped
end
```

`rbs`コマンドを使った場合は`typeprof`のように型推論はされませんが（引数も戻り値も`untyped`と出力される）、その代わりに対象のファイルを実行するプログラム（この記事でいうところのrunnerスクリプト）を用意する必要がなくなります。

### rbsコマンドでメソッドシグニチャを確認する

ちなみに、`rbs`コマンドは雛形作成以外にもいくつか機能があります。
以下は`rbs`コマンドを使ってメソッドシグニチャを確認する例です。

```
# String#rjustのシグニチャを確認する
$ rbs method String rjust
::String#rjust
  defined_in: ::String
  implementation: ::String
  accessibility: public
  types:
      (::int integer, ?::string padstr) -> ::String

# Math.sin（クラスメソッド）のシグニチャを確認する
$ rbs method --singleton Math sin
::Math.sin
  defined_in: ::Math
  implementation: ::Math
  accessibility: public
  types:
      (::Numeric x) -> ::Float

# Date#month（requireが必要な標準ライブラリ）のシグニチャを確認する
$ rbs -r date method Date month
::Date#month
  defined_in: ::Date
  implementation: ::Date
  accessibility: public
  types:
      () -> ::Integer

# Retryable.retryable（gemのクラスメソッド）のシグニチャを確認する
$ rbs --repo vendor/rbs/gem_rbs/gems -r retryable -r forwardable method --singleton Retryable retryable
::Retryable.retryable
  defined_in: ::Retryable
  implementation: ::Retryable
  accessibility: public
  types:
      [X] (?::Retryable::Configuration::options options) ?{ (::Integer, ::Exception?) -> X } -> X?
```

## テストコードとTypeProfを組み合わせてrbsを作成する

この記事では`fizz_buzz_runner.rb`のような実行スクリプトを作りましたが、Minitestのようなテストコードを実行スクリプト代わりに使ってもかまいません。

以下はテストコードからFizzBuzzクラスのrbsを生成する例です。

```test/fizz_buzz_test.rb
require 'minitest/autorun'
require_relative '../lib/fizz_buzz'

class RubyTest < Minitest::Test
  def test_run
    expected = ['1', '2', 'Fizz', '4', 'Buzz', 'Fizz', '7', '8', 'Fizz', 'Buzz', '11', 'Fizz', '13', '14', 'FizzBuzz']
    assert_equal expected, FizzBuzz.run(15)
  end
end
```

```
$ typeprof test/fizz_buzz_test.rb
# Classes
class FizzBuzz
  def self.run : (Integer) -> Array[String]
end

class FizzBuzzTest
  def test_run : -> untyped
end
```

FizzBuzzTestクラスのrbsも一緒に出力されていますが、FizzBuzzクラスも`fizz_buzz_runner.rb`を使ったときと同じようにrbsが出力されています。

## その他

### Railsで使うにはどうしたらいいの？（未検証）

pockeさん（[@p_ck_](https://twitter.com/p_ck_)）が開発されている[rbs_rails](https://github.com/pocke/rbs_rails)というgemを使うと、Rails用のrbsを生成できるみたいですが、まだ未検証です。すいません。
詳しくはrbs_railsのリポジトリや、各種Web記事を参照してください。

- https://github.com/pocke/rbs_rails
- [rbs\_railsにRailsの型を取り込む手順 2020\-07\-16版 \- pockestrap](https://pocke.hatenablog.com/entry/2020/07/16/135902)
- [rbs\_rails & steep で型のある Rails 開発を体験しよう \- stmn tech blog](https://tech.stmn.co.jp/entry/rails_with_rbs_and_steep)
- [Railsで型チェックを導入してみる \- Qiita](https://qiita.com/yasuno0327/items/7da654ed1cc8ef0e084d)

### SorbetとRBSの関係はどうなってるの？

Rubyの型チェックに以前から興味を持っていた方は、Sorbet（ソルベ）をご存じかもしれません。

https://sorbet.org/

SorbetはRBSやSteepより先行して開発・運用されている型チェックツールです。
基本的にSorbetとRBS/Steepは「Rubyの型チェックをする」という目的は同じであるものの、それを実現する仕組みは別物です。

とはいえ、SorbetとRubyの開発チームは互いに協力しながら、相互に互換性のある型チェックの仕組みを構築していく予定だそうです。

詳しくは下記のブログ記事（英語）をご覧ください。

- [The State of Ruby 3 Typing \| Square Corner Blog](https://developer.squareup.com/blog/the-state-of-ruby-3-typing/)
- [Types in Ruby 3, RBS, and Sorbet · Sorbet](https://sorbet.org/blog/2020/07/30/ruby-3-rbs-sorbet)

### 実はRuby 2.7でも使えるRBS

Steepだけでなく、RBSとTypeProfもgemとして提供されています。そのため、これらのgemさえインストールすればRuby 3.0だけでなくRuby 2.7でも動作させることが可能です。

以下はRuby 2.7.2で各ツールを動かした際の実行結果です。

```
$ ruby -v    
ruby 2.7.2p137 (2020-10-01 revision 5445e04352) [x86_64-darwin19]

$ gem install rbs
$ gem install typeprof
$ gem install steep

$ rbs prototype rb lib/fizz_buzz.rb 
class FizzBuzz
  def self.run: (untyped n) -> untyped
end

$ typeprof runner/fizz_buzz_runner.rb
# Classes
class FizzBuzz
  def self.run : (Integer) -> Array[String]
end

$ steep check
```

ちなみにTypeProfはRuby 2.7以上が必要ですが（[参照](https://github.com/ruby/typeprof/blob/v0.8.0/typeprof.gemspec#L19)）、RBSとSteepはRuby 2.6でも動作するようです（[参考-1](https://github.com/ruby/rbs/blob/v0.19.0/.github/workflows/ruby.yml#L16)、[参考-2](https://github.com/soutaro/steep/blob/v0.36.0/steep.gemspec#L29)）。

### ところでRBSって何の略？
RBSは何の略語なのか、はっきりわからないのですが、[こちらのスライド](https://speakerdeck.com/soutaro/an-introduction-to-typed-ruby-programming-8ab02a28-8af9-455d-80b8-bb8baa943cbc?slide=13)から想像するに、"Ruby Signature language"の略かな？と思ったりしています。

## RBS/TypeProf/Steepに関する情報源

この記事で紹介したのはHello, worldレベルのごく簡単なRBS/TypeProf/Steepの使い方です。
本格的にRubyで型チェックをしようとすると、もっともっと深い知識が必要になります。
より詳しく知りたい方は、以下の資料を参考にしてください。

### RBS/TypeProf/Steepの公式リポジトリ

- https://github.com/ruby/rbs
- https://github.com/ruby/typeprof
- https://github.com/soutaro/steep

各リポジトリのREADMEにはさらに詳しい情報へのリンクが載っています。

- [標準ライブラリ用シグニチャの貢献ガイド](https://github.com/ruby/rbs/blob/master/docs/CONTRIBUTING.md)
- [シグニチャの書き方ガイド](https://github.com/ruby/rbs/blob/master/docs/sigs.md)
- [stdlibシグニチャガイド](https://github.com/ruby/rbs/blob/master/docs/stdlib.md)
- [rbsの構文](https://github.com/ruby/rbs/blob/master/docs/syntax.md)
- [TypeProfの詳しい解説](https://github.com/ruby/typeprof/blob/master/doc/doc.ja.md)
- [TypeProfの使用例](https://github.com/ruby/typeprof/blob/master/doc/demo.md)

### 設計思想に触れる

RBSとSteepの開発は松本宗太郎さん（[@soutaro](https://twitter.com/soutaro)）が、TypeProfの開発は遠藤侑介さん（[@mametter](https://twitter.com/mametter)）がそれぞれ中心になって開発されています。
各ツールの設計思想はお二人の登壇スライドやWeb記事等で確認することができます。

- https://speakerdeck.com/soutaro
- https://www.slideshare.net/mametter
- [Ruby 3の静的解析機能のRBS、TypeProf、Steep、Sorbetの関係についてのノート \- クックパッド開発者ブログ](https://techlife.cookpad.com/entry/2020/12/09/120454)
- [Ruby 3 の静的解析ツール TypeProf の使い方 \- クックパッド開発者ブログ](https://techlife.cookpad.com/entry/2020/12/09/120314)

### 型定義ファイルのサンプルを探す

型定義ファイルの書き方がわからない場合は、実際の型定義ファイルを見てみると参考になるかもしれません。

- [Rubyの組み込みライブラリ（コアライブラリ）](https://github.com/ruby/rbs/tree/master/core)
- [Rubyの標準ライブラリ](https://github.com/ruby/rbs/tree/master/stdlib)
- [各種gemの型定義ファイル](https://github.com/ruby/gem_rbs/tree/main/gems)

### ブラウザ上でTypeProfを試す

下記サイトに行くと、ブラウザ上でTypeProfの実行結果を確認することができます。

https://mame.github.io/typeprof-playground/

![Screen Shot 2020-12-06 at 9.57.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7465/32bb80f6-c24a-affd-e83b-1ea6b5288889.png)

## まとめ（というか個人的な感想）

というわけで、この記事ではRuby 3.0から提供されるRBSとその関連ツールを使って、Rubyの型チェックの概要を説明しました。

個人的に他にもいくつかのユースケースでRBSを試してみたのですが、少し凝ったコードを書くと、型定義の書き方がよくわからなかったり、型チェック時に予期しない型エラー（従来の感覚からいうと、ちょっと厳格すぎるチェック）が発生してコードの修正方法に悩んだりしました。
なので、正直なところ、今すぐ開発の現場に投入できるかというと、まだちょっとハードルが高いんじゃないかなという印象です。
めちゃくちゃ柔軟で何でもできちゃうRubyだからこそ、外からカッチリとした型定義を与えるというのは、なかなか難易度が高い（柔軟さを取ると型定義が書きにくく、型定義をシンプルにすると柔軟さが失われやすい）、という感想を持ちました。

とはいえ、Rubyにようやく型チェックの土台が公式に整えられた、というのはとても大きな一歩だと思います。
これから徐々にgemの型定義が増えていったり、型定義を書くための知見や型を利用するノウハウが溜まっていたりしていくことで、数年後にはRubyでも「型のある世界」が当たり前になっているかもしれません。

[@soutaro](https://twitter.com/soutaro)さんと[@mametter](https://twitter.com/mametter)さんの活躍に感謝しつつ、今後のRubyプログラミングの進化を楽しみにしたいと思います！


### あわせて読みたい
Ruby 3.0のその他の新機能と変更点は以下の記事にまとめました。
こちらもあわせてどうぞ。

[サンプルコードでわかる！Ruby 3\.0の主な新機能と変更点 Part 2 \- 新機能と変更点の総まとめ](https://zenn.dev/jnchito/articles/24e0bd7fd1045d)