16
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

rlua を使って Rust に Lua言語 を組み込む

Posted at

はじめに

本記事は Rust にスクリプト言語を組み込む方法について調査したメモです。

本記事では弊社での実務に使用することを念頭に、

  • 簡単に使えること
  • Rust ←→ スクリプト言語間のデータのやり取りが簡単に行えること
  • パフォーマンスが悪くないこと
  • 安全であること
  • スクリプト言語のライブラリが利用できること

の観点で調査を行います。

なお、弊社では Rust からスクリプト言語を実行するケースしかないため、スクリプト言語のモジュールを Rust で実装する方法については調査を行っていません。

調査対象のクレート

今回は rlua について調べます。
rlua は Rust に Lua言語 を組み込むためのライブラリです。

Repository
amethyst/rlua: High level bindings between Rust and Lua

調査時のバージョン
rlua = "0.19.2"

前提条件

本記事では他の調査との環境の差異を無くすために、検証はすべて以下のコマンドで起動した docker のコンテナ内で行います。

docker run --rm -it \
  -v $PWD:/home \
  -w /home \
  rust:1.62.0 \
  /bin/bash

調査内容

導入方法

rlua はデフォルトで Lua言語 の処理系を内包しているため rlua を追加すればすぐに利用可能となります。

$ cargo add rlua
    Updating crates.io index
      Adding rlua v0.19.2 to dependencies.
             Features:
             + builtin-lua54
             + rlua-lua54-sys
             - builtin-lua51
             - builtin-lua53
             - lua-compat-mathlib
             - rlua-lua51-sys
             - rlua-lua53-sys
             - system-lua51
             - system-lua53
             - system-lua54

もし、 OS にインストールされた Lua が使用したい場合には system-lua54 などの feature フラグを指定します。

Rust からスクリプト言語を実行する

簡単なスクリプトの実行

まずは簡単なスクリプトの例です。
Rust と Lua のやり取りは lua.context というブロック内で実行するようになっており、 globals.set("n", n)? のように書くことで Rust のデータを Lua で利用することができます。

fn fibonacci_lua(n: i64) -> Result<i64> {
    let lua = Lua::new();
    lua.context(|lua_ctx| {
        let globals = lua_ctx.globals();
        globals.set("n", n)?;

        lua_ctx
            .load(
                r#"
                a, b = 1, 0
                for i = 1, n do
                  a, b = b, a + b
                end
                return b
                "#,
            )
            .eval()
    })
}

serde_json::Value 型のデータを渡す

Lua では JSON のような型はテーブル型というもので扱うようです。
先ほどのフィボナッチ数列の例のように globals.set でデータを渡すためには ToLua を実装していれば良いようなので、以下のようなコードを書くことで Rust の serde_json::Value の値を Lua 側でテーブル型として利用することができました。

use rlua::{Lua, Result, ToLua};
use serde_json::json;

fn json_to_table<'lua>(
    lua_ctx: &rlua::Context<'lua>,
    value: &serde_json::Value,
) -> Result<rlua::Value<'lua>> {
    Ok(match value {
        serde_json::Value::Null => rlua::Value::Nil,
        serde_json::Value::Bool(v) => rlua::Value::Boolean(*v),
        serde_json::Value::Number(v) => rlua::Value::Number(v.as_f64().unwrap()),
        serde_json::Value::String(v) => rlua::Value::String(lua_ctx.create_string(v)?),
        serde_json::Value::Array(v) => rlua::Value::Table(
            lua_ctx.create_sequence_from::<rlua::Value<'lua>, Vec<rlua::Value<'lua>>>(
                v.iter()
                    .map(|x| json_to_table(lua_ctx, x))
                    .collect::<Result<Vec<_>>>()?,
            )?,
        ),
        serde_json::Value::Object(v) => rlua::Value::Table(
            lua_ctx
                .create_table_from::<String, rlua::Value<'lua>, Vec<(String, rlua::Value<'lua>)>>(
                    v.iter()
                        .map(|(k, v)| Ok((k.to_string(), json_to_table(lua_ctx, v)?)))
                        .collect::<Result<Vec<_>>>()?,
                )?,
        ),
    })
}

struct MyJsonStruct(serde_json::Value);

impl<'lua> ToLua<'lua> for MyJsonStruct {
    fn to_lua(self, lua: rlua::Context<'lua>) -> Result<rlua::Value<'lua>> {
        json_to_table(&lua, &self.0)
    }
}

fn use_json_in_lua(data: MyJsonStruct) -> Result<bool> {
    let lua = Lua::new();
    lua.context(|lua_ctx| {
        let globals = lua_ctx.globals();
        globals.set("data", data)?;

        lua_ctx
            .load(
                r#"
                print(data)
                for k, v in pairs(data) do
                  print(k, v)
                end

                return data["age"] >= 20
                "#,
            )
            .eval()
    })
}

fn main() {
    let john = json!({
        "name": "John Doe",
        "age": 43,
        "phones": [
            "+44 1234567",
            "+44 2345678"
        ]
    });

    dbg!(use_json_in_lua(MyJsonStruct(john)));
}

スクリプト言語のライブラリを利用する

Lua言語のライブラリは LuaRocks というパッケージマネージャを利用して管理するようですが、 rlua 組み込みの Lua ではサクッと利用できるところまでは持っていけませんでした。
(モジュールのインクルードパスの問題な気がするので、もう少しで解決できそうですが。。。)

OS にインストールした Lua であれば利用可能なのですが、手軽に利用したい思いから少し外れるので一旦調査はここまでとします。

パフォーマンス

先ほどのフィボナッチ数列を求めるプログラムの実行速度を計測します。

計測に利用したコード
use rlua::{Lua, Result};
use std::time::Instant;

fn fibonacci_rs(n: i64) -> i64 {
    let (mut a, mut b) = (1, 0);
    for _ in 0..n {
        (a, b) = (b, a + b);
    }
    return b;
}

fn fibonacci_lua(n: i64) -> Result<i64> {
    let lua = Lua::new();
    lua.context(|lua_ctx| {
        let globals = lua_ctx.globals();
        globals.set("n", n)?;

        lua_ctx
            .load(
                r#"
                a, b = 1, 0
                for i = 1, n do
                  a, b = b, a + b
                end
                return b
                "#,
            )
            .eval()
    })
}

fn main() {
    let start = Instant::now();
    for n in 1..=64 {
        let _ = fibonacci_rs(n);
    }
    println!(
        "Rust の実行時間\t:{:?}",
        Instant::now().duration_since(start)
    );

    let start = Instant::now();
    for n in 1..=64 {
        let _ = fibonacci_lua(n).unwrap();
    }
    println!(
        "Lua の実行時間\t:{:?}",
        Instant::now().duration_since(start)
    );
}
$ cargo run --release
   Compiling rlua-example v0.1.0 (/home/rlua-example)
    Finished release [optimized] target(s) in 0.68s
     Running `target/release/rlua-example`
Rust の実行時間 :60ns
Lua の実行時間  :5.07335ms

より効率の良い書き方はありそうですが、気にするほど遅くは無さそうです。

安全性

先ほどからのサンプルに登場する通り、lua.contextrlua::Result 型を返します。
安全なだけではなく、lua.context 内の問題については非常に分かりやすいエラーメッセージが得られるので、デバッグも捗ります。

まとめ

rlua はパフォーマンス・安全性も問題なく、導入も容易で非常に実用的なクレートだと思いました。
懸念点があるとすれば Lua に対するメンバーの習熟度で、この点では他のスクリプト言語に分があると感じています。

16
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?