Rustでmrubyを呼んで見るライブラリminutus

# 目的
Rustでプログラム実行中に後から渡されるプログラムを実行したいです。プラグインみたいな感じです。
Rustだけで実現するのは難しいので(wasmあたりを使うとできるという噂もありますが)、他のプログラム言語を利用します。
ここではRubyを使います。

Rubyを使うものは当然Rubyがインストールされている必要があり、シングルバイナリで実行できないのが難点です。
そこでmrubyを使うことになるんですが、以前からあったものは古いmrubyしか対応していなくて微妙でした。
今回利用するminutusというライブラリは最新のmrubyをサポートしていて、mrubyの知識があまりなくてもサクッと利用できるのがよかったです。
開発の意図などは作者のブログを参考にしてください。[Minutus という mruby の Rust バインディングを作った](https://dawn.hateblo.jp/entry/2022/08/14/202458)

## コードの目的
Rubyのコードでは以下の2点を確認しました。

* 引数を渡せる
* HTTP通信ができる

# コード

以下を使うことで任意のmrubyのモジュールが組み込めます。今回は通信が確認できればいいんですが、色々入ってますが、気にしないでください。

```build_config.rb
MRuby::Build.new do |conf|
  toolchain
  conf.gembox 'default'
  conf.gem github: 'mattn/mruby-json'
  conf.gem :git => 'https://github.com/iij/mruby-pack.git'
  conf.gem :git => 'https://github.com/iij/mruby-digest.git'
  conf.gem :git => 'https://github.com/mattn/mruby-json.git'
  conf.gem :git => 'https://github.com/mattn/mruby-uv.git'
  conf.gem :git => 'https://github.com/mattn/mruby-http.git'
  conf.gem :git => 'https://github.com/matsumoto-r/mruby-simplehttp.git'
  conf.gem :git => 'https://github.com/matsumoto-r/mruby-httprequest.git'
  conf.gem :git => 'https://github.com/matsumoto-r/mruby-oauth.git'
  conf.gem :git => 'https://github.com/matsumoto-r/mruby-sleep.git'
  conf.gem :git => 'https://github.com/matsumoto-r/mruby-zabbix.git'
  conf.gem :git => 'https://github.com/matsumoto-r/mruby-growthforecast.git'
  conf.gem :git => 'https://github.com/y-ken/fluent-logger-mruby'
end
```

Rubyのコードは以下のようになります。
メソッド形式になっていてRustから引数を渡すことになります。
コメントアウトの除くときちんと標準出力に結果が表示されました。

```ruby:some_script.rb
def execute(url)
  h = HttpRequest.new
  b = h.get(url).body
  # p b
  j = JSON.parse(b)
  j.to_json
end
```

```rust:Cargo.toml
[package]
name = "mruby"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
minutus = { version = "0.4.0", features = ["mruby_3_1_0", "link_mruby"] }
serde_json = "1"

[build-dependencies]
minutus = { version = "0.4.0", features = ["mruby_3_1_0", "link_mruby"] }
```

```rust:build.rs
fn main() {
    println!("cargo:rerun-if-changed=bulid.rs");
    println!("cargo:rerun-if-changed=build_config.rb");
    minutus::MRubyManager::new()
        .build_config(&std::env::current_dir().unwrap().join("build_config.rb"))
        .mruby_version("3.1.0")
        .run();
}
```

```rust:main.rs
use std::time::SystemTime;

minutus::define_funcall!{
    fn execute(self, url: String) -> String;
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let evaluator = minutus::Evaluator::build();
    let script = std::fs::read_to_string("some_script.rb")?;
    evaluator.evaluate(&script)?;
    let main = evaluator.evaluate("self")?;
    for i in 0..5 {
        let start = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_millis();
        let retval = main.execute(format!("https://httpbin.org/get?i={}", i))?;
        let end = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_millis();
        let value = serde_json::from_str::<serde_json::Value>(&retval)?;
        println!("{}", serde_json::to_string(&value)?);
        println!("{}", end - start);
    }
    
    Ok(())
}
```

# 結果

```text:結果
{"args":{"i":"0"},"headers":{"Accept":"*/*","Host":"httpbin.org","X-Amzn-Trace-Id":"Root=1-634c8dac-79737ca70d71764e2129a0f8"},"origin":"150.91.1.193","url":"https://httpbin.org/get?i=0"}
6140
{"args":{"i":"1"},"headers":{"Accept":"*/*","Host":"httpbin.org","X-Amzn-Trace-Id":"Root=1-634c8dad-2b6d385521217ec222b742df"},"origin":"150.91.1.193","url":"https://httpbin.org/get?i=1"}
976
{"args":{"i":"2"},"headers":{"Accept":"*/*","Host":"httpbin.org","X-Amzn-Trace-Id":"Root=1-634c8dae-764a40ac0159daff07e84dc7"},"origin":"150.91.1.193","url":"https://httpbin.org/get?i=2"}
968
{"args":{"i":"3"},"headers":{"Accept":"*/*","Host":"httpbin.org","X-Amzn-Trace-Id":"Root=1-634c8daf-6689f5be0cbb4348632cc014"},"origin":"150.91.1.193","url":"https://httpbin.org/get?i=3"}
969
{"args":{"i":"4"},"headers":{"Accept":"*/*","Host":"httpbin.org","X-Amzn-Trace-Id":"Root=1-634c8db0-43cf22690ec6f43710a87ef5"},"origin":"150.91.1.193","url":"https://httpbin.org/get?i=4"}
1051
```

どうももっさりしています。何が原因かみてみます。

## JSONを疑る
本来ならJSONをRubyのオブジェクトにして加工したいこともあるんですが、今は外してみました。

```ruby
def execute(url)
  h = HttpRequest.new
  h.get(url).body
end
```

```結果
5980
1004
1001
951
956
```

変わらないです。

## 通信を疑る
```ruby
def execute(url)
  # h = HttpRequest.new
  # h.get(url).body
  # p b
  # j = JSON.parse(b)
  # j.to_json
  { url: url }.to_json
end

```

```結果
0
0
0
0
0
```
あ、これですね。

## 通信先を疑る
```
time curl "https://httpbin.org/get?i=0"
{
  "args": {
    "i": "0"
  }, 
  "headers": {
    "Accept": "*/*", 
    "Host": "httpbin.org", 
    "User-Agent": "curl/7.68.0", 
    "X-Amzn-Trace-Id": "Root=1-634c964f-36852b3112cfa5923e8ac2f2"
  }, 
  "origin": "150.91.1.193", 
  "url": "https://httpbin.org/get?i=0"
}

real    0m5.759s
user    0m0.013s
sys     0m0.007s
```

犯人はこれでした。早いときもあれば遅いときもあります。プログラムで最初だけ思いのかと思ったらそうでは無くて、接続先の問題のようでした。

# まとめ
mruby自体のパフォーマンスは問題なさそうです。

組み込みも簡単ですが、コンパイル時に利用するライブラリが決まっていないといけないので、後からmrubyでライブラリが必要になった時は再ビルドする必要があります。