クラスやインスタンス変数を使うメソッドを実装したので、まとめておく。
Rubyで実装したいコードのイメージ
module Wasabi class Object def initialize(name) @name = name end attr_reader :name def say "say, #{name}" end end end
実際のRustのコード
クラスの定義
まず、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側で呼び出す方法を調べたい。