RustでRubyのgemを書く part2

RustでRubyのgemを書く part1 の続き。

クラスやインスタンス変数を使うメソッドを実装したので、まとめておく。

Rubyで実装したいコードのイメージ

module Wasabi
  class Object
    def initialize(name)
      @name = name
    end
    attr_reader :name

    def say
      "say, #{name}"
    end
  end
end

実際のRustのコード

github.com

クラスの定義

まず、RubyのObjectをRustで使うために extern を書く。

extern {
    static rb_cObject: Value;
}

これをを継承するクラス Wasabi::Object を定義する。

let rb_class = class::rb_define_class_under(rb_mod, str_to_cstring("Object").as_ptr(), rb_cObject);

initialize の実装

第一引数はインスタンスになるため、 引数を2つ書く必要がある。

extern fn rb_object_initialize(obj: Value, name: Value) {
    unsafe {
        let name_id = util::rb_intern(str_to_cstring("name").as_ptr());
        class::rb_ivar_set(obj, name_id, name);
    }
}

rb_ivar_setインスタンス変数に引数を保存している。

name の実装

initializeで保存した値をrb_ivar_getで取得して返す。

unsafe extern fn rb_name(obj: Value) -> Value {
    let name_id = util::rb_intern(str_to_cstring("name").as_ptr());
    class::rb_ivar_get(obj, name_id)
}

say の実装

インスタンス変数の値の型はValueなので、Rustで使いやすくするため value_to_string でStringに変換する。 format! で文字列を組み立てた後、またValueに変換して返す。

unsafe extern fn rb_say(obj: Value) -> Value {
    let name_id = util::rb_intern(str_to_cstring("name").as_ptr());
    let ivar_name = class::rb_ivar_get(obj, name_id);
    let name = value_to_string(ivar_name);
    let message = format!("say, {}", name);
    string::rb_utf8_str_new(str_to_cstring(&message).as_ptr(), message.len() as c_long)
}

value_to_string の実装はこんな感じ。

fn value_to_string(value: Value) -> String {
    unsafe {
        let str = string::rb_string_value_cstr(&value);
        CStr::from_ptr(str).to_string_lossy().into_owned()
    }
}

テストコード

ここまで実装したことで、以下のテストが動作した。

RSpec.describe Wasabi::Object do
  let(:obj) { Wasabi::Object.new("foo") }

  describe "#name" do
    it "returns the initialization args" do
      expect(obj.name).to eq "foo"
    end
  end

  describe "#say" do
    it "returns the greeting message" do
      expect(obj.say).to eq "say, foo"
    end
  end
end

調査中

Rustの構造体を使う方法

せっかくRustを使っているのに、関数を使う実装になってしまっている。 Rustの構造体やメソッドをRuby側で呼び出す方法を調べたい。