• 18
    いいね
  • 0
    コメント

mruby と Rust

この記事は mruby advent calendar の 9 日目の記事です。
諸事情により大幅に公開が遅れて大変申し訳ありません。

mruby は軽量かつ柔軟な言語であるため、
Web サーバや Linux コンテナの設定用言語として用いられたり、
あるいは、サーバの構成を記述する DSL として用いられる等の、
いわゆるシステムプログラミングの領域で使用される例をよく見かけます。

しかし、従来、システムプログラミングを行うためには、
C 言語などの難解な言語を記述する必要があり、
これは mruby でシステムプログラミングをする際も例外ではありませんでした。

一方で、近年ではシステムプログラミングを行うための言語として、
Go や Rust といった、実用的ないしは、言語処理系の研究成果を集積したような言語が台頭してきています。

そこで、本稿では、まだまだ発展途上でありますが、Rust と mruby を組み合わせて利用する例についていくつか紹介をします。

mrusty

mrusty という、Rust から mruby を呼び出せるようにするためのパッケージが存在しています。

仕組みとしては、cargo build の際に build.rs が呼び出されるように仕込まれており
この build.rs の中で、gcc-rs を使用し、libmruby.a の生成とリンクを実現しています。

これは、大まかには go-mruby と同じような仕組みと言って良いでしょう。

この方法の欠点としては、libmruby.a を生成する手順が mrusty に完全に依存してしまうため、
自分の使用したい mrbgem を組み込んだ mruby を使用することが困難になる点などが挙げれれます。

また、mruby の FFI の定義も mrusty に完全に依存しているため、
mruby 自体のバージョンアップによって動作しなくなる可能性や、
バージョンアップに関する追従をどこまで行えるかといった部分に不安が残る人もいるかと思います。

とはいえ、利用方法自体は非常にシンプルなため、
自身で作った Rust のプロダクトのプログラマブルな設定用言語としての利用に関しては悪くはないかもしれません。

試しに、mrusty を使用して、第一引数に渡された mruby スクリプトを実行するプロジェクトを作成してみます。

まず、cargo コマンドを使用して実行ファイルを含む Rust プロジェクトを作成します。

cargo new mrusty-example --bin

次に、mrusty (とついでに getopts) を依存関係に追加します。

cat <<EOT >> Cargo.toml
mrusty = "*"
getopts = "*"
EOT

そして、src/main.rs を次のようにしてみます。

extern crate getopts;
extern crate mrusty;

use std::fs::File;
use std::io::Read;
use std::env::args;
use getopts::Options;
use mrusty::{Mruby, MrubyImpl};

fn main() {
    let args: Vec<String> = args().collect();

    let mut opts = Options::new();

    opts.optopt("f", "file", "target file path", "FILE");
    let matches = match opts.parse(&args[1..]) {
        Ok(m) => { m }
        Err(f) => { panic!(f.to_string()) }
    };

    let mut data = Vec::new();
    let path = matches.opt_str("f");

    let mut file = File::open(path.unwrap()).expect("Unable to open file");
    file.read_to_end(&mut data).expect("Unable to read data");

    let mruby = Mruby::new();
    let result = mruby.run(&String::from_utf8(data).unwrap()).unwrap();

    println!("{}", result.to_i32().unwrap());
}

これは、-f で指定された mruby ファイルを実行し、
実行した結果の返り値を Rust の int32 型で出力するだけの簡単なプログラムです。

これでビルドを実行してみます。

cargo build

実行する mruby ファイルを main.rb として、次のようにしてみます。

def main
  42
end

main

次のようにして実行することが出来ます。
(ちなみに main の返り値を Integer 以外にすると Panic になります)

$ target/debug/mrusty-example -f main.rb
42

また、mrusty の面白いところとして、describerequire を、
独自に実装している ところがあります。

そのため、 lib.rb というファイルを新たに作成し、
先ほどの main.rb を次のようにしても動作します。

  • lib.rb
def fib(n, a = 1, b = 0)
  if n == 0
    0
  elsif n == 1
    a
  else
    fib(n - 1, a + b, a)
  end
end
  • main.rb
require "lib"

def main
  fib(10)
end

main
$ target/debug/mrusty-example -f main.rb
55

なお上記と同じものを https://github.com/rrreeeyyy/mrusty-example に置いておきました。

mferuby

先ほどは Rust の中で mruby スクリプトを実行する Crate を紹介しました。
今度は、Rust を記述することで mruby の拡張である mrbgem を作れる、
mferuby を紹介します (ちなみに作者は mruby-cli の方です)。

仕組みとしては、mrbgem を作るためのいくつかの関数を、
Rust 側で使用するためのライブラリを用意しており
この関数を Rust 側で呼ぶことで、mrbgem を作成できるようにしています。

使用例として、作者本人が作っている mruby-rust-hello-world という mrbgem があります。

この通り、mferuby で定義されている Rust 関数を呼び出し、mrbgem を定義しています。

このように 呼び出すことで、Hello と出力されます。

mferuby で定義されている関数は多くないので、
mrbgem を作成するのに必要な全ての関数があるわけではありませんが、
定義が進んでいけば、Rust ライブラリを mruby 側から呼び出すことも十分可能だと思います。

まとめ

mrusty も mferuby もどちらも安定版といった訳ではないですが、
Rust ツールの設定用言語として使う用途や、
Rust を少し書いて mrbgem にしたい、Rust のライブラリを mruby から呼び出したいという用途では将来性がありそうな物に見えます。

C 言語を書きたくない方や Rust の勉強がしたい方、堅牢なライブラリを作ってみたい方はぜひ試してみるといいのではないでしょうか。

この投稿は mruby Advent Calendar 20169日目の記事です。