mzumi's blog

FFI を使って Ruby から Rust の関数を呼び出す(その2)

November 04, 2016

前回は、引数も戻り値もなかったので、今回は何種類か試してみようと思う。

引数や戻り値を設定する際、Ruby 側では、下記のように引数や戻り値にそれぞれ型を定義する。

# attach_function メソッドの 第1引数が対応する関数名になる。(:perform の部分)
# 第2引数の配列に、対応する関数の引数の型を定義する。(:int の部分)
# 第3引数には関数の戻り値の型を定義する(:double の部分)
# 下記の定義は、Rust 側の fn perform(i: i32) -> f64 という関数に対応する。
attach_function :perform, [:int], :double

設定可能な型の一覧はここに記述されている。
Rust 側では通常通りの関数の定義でOK。

プリミティブ型

まず、数値やブーリアンをいくつか引数で渡して、数値を返す様な関数を定義してみる。

$ cargo build --release
$ bundle exec ruby ffi_sample.rb
1, 2.3, true
2

これは、そのまま使えるようだ。

文字列

次に、文字列を渡して、文字列を返す関数を定義してみる。
Rust 側のプログラムでは、先ほどとは違いRuby から渡せれた文字列を Rust の文字列に変換する必要がある。
また、文字列を返す場合にも、Rust の文字列から Ruby の文字列に変換する必要がある。(正確には、Ruby の文字列ではなく、C の文字列だと思うが)

Ruby から渡された文字列は C の文字列として渡ってくるため、libc の c_char を使用する。
で、渡されたポインタを CStr を使い、Ruby から渡された文字列を Rust の文字列に変換する。
CStr は 借用している C の文字列表現したものになる。
似たような構造体に CStringにあるが、こちらは所有権を保持している場合の C の文字列表現。
戻り値では、Rust の文字列を C の文字列に変換するが、こちらは、所有権を保持している文字列から、C の文字列に変換するため、CStringを使用している。

このプログラムを実行すると

$ cargo build --release
$ bundle exec ruby ffi_sample.rb
こんにちは、世界

となる。

構造体

次は、構造体を使ってみる。
例として、引数と戻り値が構造体になる関数を定義してみる。

Rust と Ruby で同じ構造体をそれぞれ定義する。
Ruby 側では、FFI::Struct を継承したクラスを作成し、layout メソッドで、メンバーを定義する。
Rust 側では、C の構造体と互換性を保証するために、repr(C) をつけて、struct を作成。

実行結果は、下記のようになる。

$ cargo build --release
$ bundle exec ruby ffi_sample.rb
i: 2, j:3

Ruby 側のメソッドの引数と戻り値に by_value というメソッドで指定しているが、これは構造体を値渡しにするといった指定になる。参照渡しにする場合には、by_ref というメソッドで指定すれば、参照渡しになる。
参照渡しについては、ポインタによる指定もできるのだが、ポインタについてはまた次回。