5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2021-12-15

はじめに

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

それでもFFIの使い方は覚えて、C言語の関数をRubyから呼べるようになりました。いつかはC拡張を作りたいなと考えていましたが、先週ようやくはじめてlib2bitというライブラリのバインディングをPythonから移植することができました。

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

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

はい。これです。

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

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

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

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

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

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

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

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

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

例 magro

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

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

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 であることが書かれているんだね。

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

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

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

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

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

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

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

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

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

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

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

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

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

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かどうか確認してエラーを発生させる処理が頻繁に入ります。

  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変換、処理、リターン の各部分で改行する

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

この記事は以上です。

5
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?