はじめに
効率的で信頼できるソフトウェアを誰もがつくれる言語
引用元
Rustler を使うことで、 Elixir から Rust のコードを呼べるようになります
本来であれば mix new
で作った Elixir のプロジェクト内で使用するものですが、無理矢理 Livebook で動かしました
Livebook では Rustler 経由での Rust のビルドができなかったため、 Rustler 内部で行なっていることを Livebook で実行しています
ちゃんと Rustler を使っている例は @pojiro さんの記事をご覧ください
本記事では Livebook で(一部) Rustler を使い、 Rust のコードを実行します
実装したコードはこちら
前提条件
実行環境には Rust をインストールしておきます
セットアップ
セットアップセルで Rustler をインストールします
Mix.install([
{:rustler, "~> 0.35.1"}
])
Rust プロジェクトの生成
Rustler を使って Rust のプロジェクトを生成します(cargo new
と同じようなこと)
そのまま実行すると Livebook 実行環境のホームディレクトリーに作られてしまうため、 /tmp
に移動します
File.cd("/tmp")
Elixir 側のモジュール名 module
と Rust 側のプロジェクト名 name
を定義しておきます
module = "LearnRustler.Nif"
name = "learn_rustler_nif"
Rust のプロジェクトを作成します
Mix.Tasks.Rustler.New.run(["--module", module, "--name", name])
上記のコードは mix rustler.new --module LearnRustler.Nif --name learn_rustler_nif
を実行したのと同じことになります
標準出力には以下のように表示されます
* creating native/learn_rustler_nif/.cargo/config.toml
* creating native/learn_rustler_nif/README.md
* creating native/learn_rustler_nif/Cargo.toml
* creating native/learn_rustler_nif/src/lib.rs
* creating native/learn_rustler_nif/.gitignore
Ready to go! See /tmp/native/learn_rustler_nif/README.md for further instructions.
生成されたプロジェクトのパス native_path
と、その中の src/lib.rs
のパス src_path
を定義しておきます
native_path = Path.join([File.cwd!(), "native", name])
src_path = Path.join([native_path, "src", "lib.rs"])
src/lib.rs
の中身を実装したい内容に書き換えます
今回は加算、乗算、ソート、挨拶の関数を Rust で実装しました
File.write!(src_path,"""
#[rustler::nif]
fn add(a: i32, b: i32) -> i32 {
a + b
}
#[rustler::nif]
fn multiply(a: i32, b: i32) -> i32 {
a * b
}
#[rustler::nif]
fn sort(v: Vec<i32>) -> Vec<i32> {
let mut sorted = v;
sorted.sort();
sorted
}
#[rustler::nif]
fn hello(name: &str) -> String {
format!("Hello, {}", name)
}
rustler::init!("Elixir.LearnRustler.Nif");
""")
Rust プロジェクトのビルド
cargo rustc --release
を実行し、 Rust プロジェクトをビルドします
System.cmd(
"cargo",
["rustc", "--release"],
cd: native_path,
stderr_to_stdout: true,
into: IO.stream(:stdio, :line)
)
標準出力
Compiling proc-macro2 v1.0.92
Compiling unicode-ident v1.0.14
Compiling regex-lite v0.1.6
Compiling heck v0.5.0
Compiling cfg-if v1.0.0
Compiling inventory v0.3.16
Compiling libloading v0.8.6
Compiling rustler v0.35.1
Compiling quote v1.0.38
Compiling syn v2.0.92
Compiling rustler_codegen v0.35.1
Compiling learn_rustler_nif v0.1.0 (/tmp/native/learn_rustler_nif)
Finished `release` profile [optimized] target(s) in 8.39s
ビルドして作成された liblearn_rustler_nif.so
のパスを定義しておきます
lib_path = Path.join([native_path, "target", "release", "liblearn_rustler_nif"])
NIF の読込
Elixir 側のモジュールを定義し、 load
関数で NIF を読み込みます
defmodule LearnRustler.Nif do
def load(path), do: :erlang.load_nif(path, 0)
def add(_a, _b), do: :erlang.nif_error(:nif_not_loaded)
def multiply(_a, _b), do: :erlang.nif_error(:nif_not_loaded)
def sort(_v), do: :erlang.nif_error(:nif_not_loaded)
def hello(_name), do: :erlang.nif_error(:nif_not_loaded)
end
LearnRustler.Nif.load(lib_path)
関数の実行
定義した関数を実行します
LearnRustler.Nif.add(3, 4)
実行結果
7
LearnRustler.Nif.multiply(3, 4)
実行結果
12
LearnRustler.Nif.sort([8, 5, 6, 2])
実行結果
[2, 5, 6, 8]
LearnRustler.Nif.hello("Rust")
実行結果
"Hello, Rust"
全て想定通りに動きました
まとめ
Livebook 上で Rust のコードを実行できました
本来、 Rustler を使う場合は以下のうようなモジュールを定義しておくだけで良いのですが、 Livebook の場合は :otp_app
に指定するアプリケーション名が存在しないため、この方法は諦めました
defmodule LearnRustler.Nif do
use Rustler, otp_app: :learn_rustler, crate: "learn_rustler_nif"
def add(_a, _b), do: :erlang.nif_error(:nif_not_loaded)
end
そのおかげで Rustler の中身を見て内容を理解できたので、むしろ良かったと思います