エンジニアのソフトウェア的愛情

または私は如何にして心配するのを止めてプログラムを・愛する・ようになったか

Rustlerを使ってElixirのNIFを書たときに異なる型の要素を含むリストをあつかいたいばあいの覚書

結論: Vec<Term<'a>> 型にデコードするとよい

リスト全体は Vec<T> 型で受けるとして、T になにを書けばよいのかしばし悩んだのですが、Erlant Term のままあつかうのでよければ Term<'a> 型で受ければよいということに気がつきました。

fn count<'a>(env: Env<'a>, args: &[Term<'a>]) -> Result<Term<'a>, Error> {
    let list: Vec<Term<'a>> = args[0].decode()?;

    Ok(list.len().encode(env))
}

各要素の具体的な値を利用したいばあいは、おのおの必要になった時点でデコードすればよさそうです。

実例

結論を書いてしまったので、以下は Rustler を使う手順の一般的な覚書です。

Rustler を導入する

Rustler は Rust で NIF を書くための便利なライブラリです。 上に書いたコードのように、とても簡単に Rust と Elixir をインタフェースしてくれます。

hex.pm

プロジェクトを作成し、依存するパッケージに Rustler を追加します。

$ mix new my_app
$ cd my_app
  defp deps do
    [
      {:rustler, "~> 0.21"}
    ]
  end

パッケージを導入すると mix rustler.new コマンドが利用できるようになります。

$ mix do deps.get, deps.compile
$ mix help
...
mix rustler.new         # Creates a new Rustler project
...

NIF の雛形を生成する

追加された mix rustler.new コマンドを使っって NIF の雛形を生成します。

モジュール名を訊かれるので入力します。

続けてライブラリ名を訊かれます。 モジュール名から生成した名前が示されるので、その名前でよければそのまま決定します。

native という名前のディレクトリの下に指定したライブラリ名でパッケージが作成されます。

$ mix rustler.new
This is the name of the Elixir module the NIF module will be registered to.
Module name > MyApp.List
This is the name used for the generated Rust crate. The default is most likely fine.
Library name (myapp_list) >
* creating native/myapp_list/.cargo/config
* creating native/myapp_list/README.md
* creating native/myapp_list/Cargo.toml
* creating native/myapp_list/src/lib.rs

Rust のコードを編集する

native/myapp_list/src/lib.rs を編集します。 ここではリストを受け取って要素の数を返す関数を書きます。

要素の数だけを知りたいので、今回は Erlang Term のままであつかい個々の要素はデコードしません。 このため先に書いたとおり、受け取った引数のリストを Vec<Term<'a>> という型にデコードします。

use rustler::{Encoder, Env, Error, Term};

fn count<'a>(env: Env<'a>, args: &[Term<'a>]) -> Result<Term<'a>, Error> {
    let list: Vec<Term<'a>> = args[0].decode()?;

    Ok(list.len().encode(env))
}

rustler::rustler_export_nifs! {
    "Elixir.MyApp.List",
    [
        ("count", 1, count)
    ],
    None
}

NIF をマウントする Elixir のモジュールを作成する

Rust のコードに書かれたモジュール名の Elixir のモジュールを作成します。

モジュールでは Rustler を use し、オプションで OTP アプリケーション名と、Rust のライブラリ名を指定します。

defmodule MyApp.List do
  use Rustler, otp_app: :my_app, crate: :myapp_list

  def count(_list), do: :erlang.nif_error(:nif_not_loaded)
end

コンパイラとパッケージを指定する

mix.exs を編集してコンパイラに Rustler を追加し、利用するパッケージの指定を追加します。

  def project do
    [
      app: :my_app,
      version: "0.1.0",
      elixir: "~> 1.11",
      start_permanent: Mix.env() == :prod,
      compilers: [:rustler | Mix.compilers()], # 追加 - コンパイラに Rustler を追加する
      rustler_crates: [myapp_list: []],        # 追加 - myapp_list を利用するパッケージに追加する
      deps: deps()
    ]
  end

実行

実行します。 コンパイラに Rustler を追加しているので、Elixir のコードがコンパイルされるのと一緒に Rust のパッケージもコンパイルされます。

$ mix run -e 'MyApp.List.count([1, :ok, {}, [], "ABC"]) |> IO.inspect()'
Compiling NIF crate :myapp_list (native/myapp_list)...
    Finished release [optimized] target(s) in 0.19s
Compiling 2 files (.ex)
Generated my_app app
5

いつか読むはずっと読まない:ラスト・モンスターに泣かされるレベル1パーティー

なけなしのお金で購入した金属製のアイテムをことごとく錆びつかせるラスト・モンスターが、キャリオン・クロウラーと並んで低レベルパーティーの憎き敵役だったのもよい思い出。

ダンジョンズ&ドラゴンズ スターター・セット第5版

ダンジョンズ&ドラゴンズ スターター・セット第5版

  • 発売日: 2017/12/18
  • メディア: おもちゃ&ホビー

実践Rust入門[言語仕様から開発手法まで]

実践Rust入門[言語仕様から開発手法まで]