RubyのC拡張はなぜ難しいのか？初心者を阻む２つの壁と突破方法

## はじめに

Rubyにはじめて触ってから、もう10年近い月日が経とうとしています。月日の速さが信じられない思いです。
Rubyに触れている中で、なかなか突破できなかった一つの壁がRubyのC拡張の作成でした。

それでも[FFI](https://github.com/ffi/ffi)の使い方は覚えて、C言語の関数をRubyから呼べるようになりました。いつかはC拡張を作りたいなと考えていましたが、先週ようやくはじめて[lib2bit](https://github.com/dpryan79/lib2bit)というライブラリの[バインディング](https://github.com/ruby-on-bioc/twobit)をPythonから移植することができました。

そのなかで、なぜ初心者がC拡張を作るのか難しいのか壁が見えてきたので、記録に残しておきたいと思います。
（こういうのは一旦できるようになってしまうとすぐに忘れてしまうものなので）

## 理由その１　C言語の関数と、Rubyのメソッドの対応関係はCファイルの一番下に記載されている

はい。**これ**です。

これが、わたしがC拡張を、今日まで書けなかった最大の理由、だと思います。

初心者が直面する**最大の壁**です。

どういうことかというと、RubyのC拡張って、Rubyのあるメソッドを、C言語の関数で実装するという発想からはじまります。

一般的に、RubyのC拡張に入門する人たちは、この処理をC言語で書いたら早くならないだろうか、とか、このライブラリをC言語で呼びたいなというところから興味を持つと思うんですよね。あるいは、Rubyのメソッド、C言語でどう書かれているんだろう？という疑問を持って、他の人が書いたライブラリを閲覧しようと考えると思います。

#### 初心者はRubyのメソッドと、C言語の関数の対応を知りたい

で、Githubにたどりついた初心者は、C拡張の入っている`ext` ディレクトリを見に行きます。そして、ソースコードを１行目から読み始めるのですが、もともとC言語に不慣れなのでさっぱりわかりません。そして、上から1/4ぐらいスクロールして、C言語は長いなよくわからないと思ってタブをクローズしてしまう。

でも、これだと絶対にC拡張はわかるようになりません。なぜならば、通常Rubyのメソッドと、C言語の関数の対応というのは、Cファイルの一番下に書かれているからです。だから、最後までスクロールしないとRubyのメソッドとC言語の関数の対応がわからないんです。そこで、

# RubyのC拡張を読むときは、まずは、Cファイルを最後までスクロール！！！！

これがこの記事で一番伝えたいことです。そのファイルに、どんなRubyのメソッドに対応するCの関数が書かれているか把握する。C拡張を作りたくても入口がわからない数年前の自分に言いたい。それは、Cファイルを最後までスクロールしていないからだ。**答えは一番最後に書いてある！！！**

#### 例 magro

例をあげます。
画像ファイルを読んで行列に変換したり、行列から画像ファイルに変換する magro というライブラリがあります。そのC拡張のコードを見ると、

https://github.com/yoshoku/magro/blob/main/ext/magro/imgrw.c

全410行の一番最後の7行にその情報が書かれています。

```c
void init_io_module() {
  VALUE mIO = rb_define_module_under(mMagro, "IO");
  rb_define_module_function(mIO, "read_png", magro_io_read_png, 1);
  rb_define_module_function(mIO, "save_png", magro_io_save_png, 2);
  rb_define_module_function(mIO, "read_jpg", magro_io_read_jpg, 1);
  rb_define_module_function(mIO, "save_jpg", magro_io_save_jpg, -1);
}
```

ここに、`Magro::IO` というモジュールに、`read_png`, `save_png`, `read_jpg`, `save_jpg` の4つのメソッドが追加されて、おり、それぞれ対応するCの関数が、`magro_io_read_png`, `magro_io_save_png`, `magro_io_read_jpg`, `magro_io_save_jpg` であることが書かれているんだね。

大事な情報は、ファイルの一番最後に書いてある。だから、最後までスクロールしよう。

## 理由その２　static VALUEとかいう大量に出てくる謎のキーワード

ここから先は、理由その１と比べるとはるかに小さな壁ですが、個別に解決していきます。

C言語の、hello world は多くの人が書いたことがあると思います。

それでもRubyのC拡張を読もうとすると知らないキーワードがでてきます。中でも、初心者に「わからない」と感じさせるキーワードが `static VALUE` だと思います。

わからないものが1つだけ出てきた場合、それを調べることで対処しようと思いますが、わからないものが同時に２つ出てきてしまうと、人は反射的に思考を停止してしまいがちです。唐突に出現する `static VALUE` はまさにそれに該当します。

先に `static` から潰します。これは [C言語 staticを変数と関数に付ける価値【保護の仕組みを解説】](https://monozukuri-c.com/langc-funclist-static/)によると、他のファイルから変数や関数を参照することを禁止するためのキーワードだそうです。他のファイルから関数や変数を参照されないことで安全性が高まるそうです。Rubyだとプライベートメソッドに似ているかもしれません。

初心者は、あまり複数のファイルからなるC言語のコードを書かないから、これまで `static`を使う機会がなかったわけですね。つまり、`static`自体はそれほど大した意味はない。

次に `VALUE` です。これもあまり難しくありません。Rubyのオブジェクトの実態は構造体です。そしてこのRubyのオブジェクトつまりC言語の構造体は、`VALUE型`という名前がついているそうです。int型やdouble型のように、Rubyのオブジェクト型というのがあって、それがVALUEです。本当は少し違うのかも知れませんが、初心者的には雑に`VALUE型 = Ruby型` と理解しています。（Ruby型という言葉はありませんが）

なぜ初心者がVALUE型につまづくのか。それは単に「VALUEという英単語からRubyのオブジェクトを連想するのが難しい」「今までに見たことのない大文字のキーワードから圧を感じる」からです。

そして、次のような記述はだいたい

```ruby
static VALUE rb_utf8_str_new(const char *str, long length)
{
  return rb_enc_str_new(str, length, rb_utf8_encoding());
}
```

このように2行で書かれます。

```ruby
static VALUE
rb_utf8_str_new(const char *str, long length)
{
  return rb_enc_str_new(str, length, rb_utf8_encoding());
}
```

初心者は唐突に出てくる `static VALUE` に驚かされますが、これは**単に関数の定義が2行になっているだけ**です。深い意味はない。

staticは他ファイルからの参照の禁止してるだけ、`VALUE型` はRuby型(そんな用語はない)。これで全て解決しました。

ほかにもポイントはたくさんあると思いますが、「初心者の壁」になりやすいのはこの2点だと思いました。

この記事のメッセージは以上ですが、気がついたことをリストアップしていきます。

### C言語ではNULLの確認をするコードを入れる

NULLかどうか確認してエラーを発生させる処理が頻繁に入ります。

```c
  if (!tb)
  {
    twobitClose(tb);
    rb_raise(rb_eRuntimeError, "Could not open file %s", path);
    return Qnil;
  }
```

ひとつ処理をしたら、NULLかどうか確認するのが安全そうです。

### C言語では`goto`をエラー処理に使う

`goto error;` として、最後に `error:` で `rb_raise` するパターンがあります。

### C拡張を書くときは、意図的にRubyよりも改行を増やそう

これは個人的な意見で、初心者に限った話かも知れませんが、私の場合、C拡張を書くときは意識して改行を増やした方が良いと感じました。感覚的にはRubyを書いているときの2倍〜3倍の改行を入れた方がいいと感じます。そうしないと自分の場合、書いたコードが読みにくくなります。もちろんC言語に習熟した人は意識する必要はないと思いますが。

### 宣言、C←→Ruby変換、処理、リターン の各部分で改行する

では、どこで改行を入れるべきかと言うと、ずばり、関数を４つの部分 `宣言`, `Ruby←→Cの変換`、`処理`、`リターン` に分割して、それぞれの区切りに改行を入れると良いように感じました。特にバインディングの場合は、かならずこの順番に処理すると思いますので、改行を入れると処理の流れがわかりやすくなると思います。さらに必要に応じて適宜NULLチェックやエラー処理のところでも改行を入れていきます。そうすると結果的にRubyの2倍ぐらい改行していることになると思います。

この記事は以上です。