10
6

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 3 years have passed since last update.

実況: mutableなストレージのNIFsを実装してみる

Last updated at Posted at 2019-09-17

#まえがき
どういう訳か小生の回り(電車で1時間30分の範囲)では、Elixirが熱い!そして Nervesが熱い!!

そんな訳でElixirを触ってはみるものの……組込系を生業としているが故に、Functional×パイプの特色にも、WebフレームワークのPhoenixにも、はたまたエコシステムのNervesHubにも、いまのところ「あ、そう。よかったね」程度の感想しか湧かないのである。Elixirの雰囲気を一通り味見した今、興味は専ら他言語拡張機能NIFs(Native Implemented Functions)に向かってしまうのである。

本稿は、そんな異端な記事なので、まっとうなElixir使いやよい子は真似しないように…

#お題目
さてNIFsで何を作ろうか?

ふむ、どうせ異端ならば、immutable/参照透過性を良しとする関数型言語の禁を破り、mutableなデータ構造fifo[*1]を実装してみよう:stuck_out_tongue_winking_eye:

= FIFOモジュールの仕様 =
    1. FIFO.create()        : 新たにfifoを一つ作り、そのハンドルを %FIFO{handle: h} で返す
    2. FIFO.front(%FIFO)    : fifoの頭の要素を覗き見て返す。fifoが空の場合はnilを返す
    3. FIFO.get(%FIFO)      : fifoの頭の要素を取り外して返す。fifoが空の場合はnilを返す
    4. FIFO.put(%FIFO, item): fifoのお尻にitemを追加する
    5. FIFO.count(%FIFO)    : fifoの中の要素の個数を返す
    6. FIFO.clear(%FIFO)    : fifoを空にする

こんなところかな…

[*1]実は、Erlangには double-ended fifo queueを実装した queueモジュールが用意されている。Elixirからは:queue.ほにゃららで利用できるのだ。この queueの実装は「"Purely Functional Data Structures" by Chris Okasaki」に載っているものと同等かな?

#めあて
日頃、息子たちに「過ぎ去った時間は、金をいくら積んでも取り戻すことができない」と口酸っぱく言っている手前、「NIFsを書いてみました。試してみました」と戯れただけに終わると示しがつかない。 オヤジの威厳を保つために、それなりの取り組み課題を設定しておこう:sweat_smile:

課題1. "Resource objects"の使い道の理解、C++でのコードの書き方の模索
課題2. thread safeなNIFsの書き方の模索
課題3. Elixirの任意のデータをNIFsで受け取って扱う方法の模索

#起: プロジェクトを用意する
まずは、いつもの様に mixでプロジェクトを用意する。

bash
> mix new fifo
> cd fifo

さて、NIFはC++で実装するので、C++のソースコードを入れておくディレクトリが必要になる。

Erlangの"Directory Structure Guidelines" [5] によると、ディレクトリ名は "cc_src"とするのが正統派の様だ。
しかし、いろいろと手抜きをしたいが故に Nervesの Community libraries / circuits_gpio [4] に倣って "src"としておく。

作成するNIFのC++ソースコードのファイル名は "fifo_nif.cc"としよう。

bash
> mkdir src
> touch src/fifo_nif.cc

"fifo_nif.cc"は、g++でコンパイル/リンクする。
今回は1ファイルだけなので、エイヤッと g++でビルドしても良いのだが、後学のために makeで行う。
mixから makeを呼び出すタスクは、既にHexに登録されていて elixir_makeだ。ありがたや:blush:
elixir_makeの Usageに従って、"mix.exs"の projectと depsに下記の設定を追加する。
そして、おもむろに mix deps.getを実行して、elixir_makeをGitHubからダウンロードしておこう。

mix.exs
  def project do
    [
       
      compilers: [:elixir_make] ++ Mix.compilers,
       
    ]
  end
   
  defp deps do
    [
      {:elixir_make, "~> 0.4", runtime: false}
    ]
  end

bash
> mix deps.get

ふぅむ。【起】の最後の難関は Makefileの記述だ。

ここは手抜きをして、先出の circuits_gpioの Makefileを元に、必要な変更を加え、また不要な記述を削る方針で進める。

シンボル PREFIXと BUILDは、それぞれビルドで出来上がった動的ライブラリ(.so)と、その中間ファイル(.o)の格納先ディレクトリを表している。具体的には、

  • PREFIX = _build/dev/lib/fifo/priv
  • BUILD = _build/dev/lib/fifo/obj

となる。これはそのまま頂こう。

動的ライブラリを作りたいので、gcc/g++ではコンパイラ・オプションに -fPIC、リンカ・オプションに -fPIC -sharedを指定するのはお約束。これらを シンボル CFLAGS, LDFLAGSの定義に追加する。

NIFsのビルドでは、Erlang由来のライブラリをリンクする必要はなく、ヘッダーファイル "erl_nif.h"さえ参照できれば良い。故に、シンボル ERL_LDFLAGSの定義の -L$(ERL_EI_LIBDIR)は不要なのだが、実害は無さそうなので取り敢えず残しておこう。

その他、サフィックス .cを .ccに置換したり、 $(CC)"を $(CXX)に置換したりして、以下の様になった。
ふぅ…

Makefile
# Makefile for building the NIF
#
# MIX_APP_PATH  		path to the build directory
# ERL_EI_INCLUDE_DIR	include path to erlang header
# ERL_EI_LIBDIR			path to erlang/c libraries (Not necessaly for NIFs)

PREFIX = $(MIX_APP_PATH)/priv
BUILD  = $(MIX_APP_PATH)/obj

NIF = $(PREFIX)/fifo_nif.so

CFLAGS  ?= -O2 -Wall -Wextra -Wno-unused-parameter -pedantic -fPIC
LDFLAGS += -fPIC -shared

# Set Erlang-specific compile and linker flags
ERL_CFLAGS  ?= -I$(ERL_EI_INCLUDE_DIR)
ERL_LDFLAGS ?= -L$(ERL_EI_LIBDIR)

SRC = src/fifo_nif.cc
HEADERS =$(wildcard src/*.h)
OBJ = $(SRC:src/%.cc=$(BUILD)/%.o)

all: install

install: $(PREFIX) $(BUILD) $(NIF)

$(OBJ): $(HEADERS) Makefile

$(BUILD)/%.o: src/%.cc
	$(CXX) -c $(ERL_CFLAGS) $(CFLAGS) -o $@ $<

$(NIF): $(OBJ)
	$(CXX) -o $@ $(ERL_LDFLAGS) $(LDFLAGS) $^

$(PREFIX):
	mkdir -p $@

$(BUILD):
	mkdir -p $@

clean:
	$(RM) $(NIF) $(BUILD)/*.o

.PHONY: all clean install

と言うことで、プロジェクトのディレクトリ構造はこうなる。

fifo/
├──_build/
│   ├── dev/
│   │   ├── lib/
│       │   ├── fifo/
│           │   ├── obj/    -- C++ソースをコンパイルしたオブジェクトファイル(.o)の格納先
│               └── priv/   -- C++ソースをコンパイル/リンクして出来た動的ライブラリ(.so)の格納先
├── deps/
├── lib/
│   └── fifo.ex   -- Elixir側のインターフェイス
├── src/
│   └── fifo_nif.cc   -- C++で記述したNIFのソースコード
├── test/
├── Makefile   -- C++をビルドする為のメイクファイル
├── README.md
└── mix.exs

#承: FIFO NIFの実装
本題である。
FIFOの実体はオーソドックスに STL std::dequeにする。
否、出来合いの std::dequeで手抜きしたいが故にわざわざ C++でNIFを記述することにしたのだ。

手抜きなので、当然 std::dequeの Allocatorはデフォルトのままで使う。C++11以降ではthread safeになっている様だし、たぶん大丈夫だろう。手抜きにはリスクが伴うものだ:stuck_out_tongue:

さて、FIFOの実体が std::dequeに決まったことで、考えるべきことが 2つある。FIFOの**(1)生成/破壊の制御(2)thread safe化**だ。そう、「めあて」に掲げた課題1と課題2だ。

####(1)FIFOの生成/破壊の制御
std::dequeを使うには、その前にどこかでstd::dequeのインスタンスを生成しなきゃならない。今回は、Elixirのコードの中で自由に使いたいので、コードの文脈から独立した寿命を持つインスタンスが必要だ。つまり、ヒープメモリからアロケート(new)することになる…

おっと、厄介ごとの始まりだ。ヒープメモリからアロケートしたインスタンスは、要らなくなったら必ずデリートしなければならない。そうしなければ、メモリ・リークが発生して、遠い将来にシステムがハングアップするかも知れない。GCを持たないC/C++のエンジニア泣かせな面だ。

愚痴を言っても始まらないので、とっとと設計しよう。設計の選択肢としては2つある。

  1. デリート用のNIF関数を用意し、Elixir側から明示的に不要になったFIFOをデリートする
  2. なんらかの仕組みで不要になったFIFOを見つけ、それが自動的にデリートされるようにする

インプリメントが簡単なのは選択肢1だ。だが、依然としてメモリリークのリスクが残るうえに不細工。

では選択肢2は? "なんらかの仕組み"を Elixir/Erlangが提供してくれなきゃ始まらないが、果たしてそんな仕組みがあるのか?

…あった! "Resource objects"がそれだ。
Erlang erl_nifのリファレンスマニュアルの "Resource objects"のパラグラフにこうある。

A resource type can have a user-supplied destructor function, which is automatically called when resources of that type are released (by either the garbage collector or enif_release_resource).

ふむふむ、NIFsのロード初期化関数load()の中で、"resource type"を enif_open_resource_type()で定義する。同時に destructorを登録か。こんな感じだな。

Resourceの使い方(その壱)
static static ErlNifResourceType* _ResType_DEQUE;  // resource type

void fifo_destroy(ErlNifEnv* env, void* ptr)
{
    MyDeque* deque_ptr = reinterpret_cast<MyDeque*>(ptr);
    deque_ptr->~MyDeque()   //インスタンスの破壊,メモリブロックの開放はElixir/Erlangが行う
}

int load(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM load_info)
{
    _ResType_DEQUE = enif_open_resource_type(env, NULL, "myfifo", fifo_destroy, ERL_NIF_RT_CREATE, NULL);
    return 0;
}

一方で、インスタンスの生成は?

まず "resource type"を引数に enif_alloc_resource()でメモリブロックをアロケートする。そのメモリブロックに placement newで std::dequeをインスタンス化すれば良いかな。インスタンスの寿命をElixir/Erlangに委ねるために、enif_release_resource()で参照カウントを一つ減らしておくのを忘れずに。

Resourceの使い方(その弐)
ERL_NIF_TERM fifo_create(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
    MyDeque* deque_ptr
        = new(enif_alloc_resource(_ResType_DEQUE, sizeof(MyDeque))) MyDeque;  // placement new

    ERL_NIF_TERM term = enif_make_resource(env, deque_ptr);  // makeすると漏れなく 参照カウント=1 となる
    enif_release_resource(deque_ptr);  // 参照カウントを一つ減らす

    return term;
}

####(2)FIFOのthread safe化
残念ながら Elixir/Erlangは、Nifs そして"Resource objects"を thread safeには扱ってくれない。

以前、マルチメディア・ライブラリSMFL(OpenGL)の NIFsを実験的に書いたことがあるが、随分と痛い目にあった。Elixir/Erlangの最下層では、数個のthreadが並行に動いており、どのthreadから何時 NIFsがcallされるかは決まっていないのだ。

ならば NIFs側で自己防衛するしかあるまい。Mutexの出番である。

erl_nifのAPIにもMutexのオペレーションが用意されているが、STLの std::mutexに比べ少々扱いが面倒なので、お手軽な std::mutex & std::lock_guardを使うことにしよう。

Mutexによる排他制御が必要なコード・ブロックは、(1)std::dequeを破壊的に書き換えるコード塊と、(2)複数のstd::dequeメソッドをアトミックに実行したいコード塊だ。たとえばこう…

ERL_NIF_TERM fifo_pop(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
  <>
   // mutual exclusion after here
    std::lock_guard<std::mutex> lock(deque_ptr->m_mutex);

    // empty(),front(),pop_front()をアトミックに実行
    if (!deque_ptr->m_deque.empty()) {
        ErlNifBinary bin = deque_ptr->m_deque.front();
        deque_ptr->m_deque.pop_front();
        <>
    }
    <>
}

####(3)データをNIFsで受け取る方法
ここまでで、なんとか課題を2つクリアできた:blush:

残るはあと一つ…「Elixirの任意のデータをNIFsで受け取って扱う方法」だ。

はっきり言ってこの課題は、小生の好奇心を満たすのが目的で、それ以上でもそれ以下でもない。超個人的な拘りに過ぎない:stuck_out_tongue_winking_eye:

さてと。Erlang erl_nifのリファレンスマニュアルによると、Elixir/ErlangからNIFs関数をcallする際、その引数データは ERL_NIF_TERM型に変換して渡される。NIFs側では、APIの erl_get_***関数群を使って渡された ERL_NIF_TERMから元のデータを復元して使用することになる。こんな感じだ…

erl_get_***の例
ERL_NIF_TERM foo(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
    <>
    double prm;
    if (!enif_get_double(env, argv[0], &prm)) {
        return enif_make_badarg(env);
    }
    double twice = 2.0 * prm;
    <>
}

だが、これでは課題はクリアできない。上のコード例では、予め設計で決めたデータ型しか受け取ることができない。つまり、受け取るデータをdouble型と決めると、double型しか格納できないFIFOになってしまう。

そんなつまらないものを作ってどうする!
やりたいことは、double型でも、int型でも、リスト型でも Elixirで定義できるデータならば何でもFIFOに放り込みたいのだ。

さて、どうする?
あれこれと試行錯誤した結果、どうやら ERL_NIF_TERMを binaryとして受け取れば良さそうだ。使用する APIは enif_term_to_binary()。enif_term_to_binary()は、受け取った ERL_NIF_TERMを解析し、Erlang external term formatのRAWデータを生成するようだ。少々効率が悪そうなところには目を瞑ろう。

enif_term_to_binary()の例
ERL_NIF_TERM baa(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
    <>
    ErlNifBinary bin;
    if (!enif_term_to_binary(env, argv[0], &bin)) {
        return enif_make_badarg(env);
    }
    <>
}

Wrap-up

無事、最後の課題もクリアした。

ここまでの成果と併せて、FIFOの仕様に謳った機能をインプリメントすると、NIFsのコードはこうなる。

firo_nif.cc
/***  File Header  ************************************************************/
/**
* @file fifo_nif.cc
*
* Helper templates for handling NIFs resource object
* @author   Shozo Fukuda
* @date     Create: Thu Sep 12 16:12:13 JST 2019
* System    Linux Mint<br>
*
* Produced by Shozo Fukuda
*
**/
/**************************************************************************{{{*/

/***** INCLUDE *****/
#include <erl_nif.h>
#include <deque>
#include <algorithm>
#include <mutex>

/***** CONSTANT *****/

/***** TYPE *****/
struct DequeNifBinary {
	std::mutex               m_mutex;
	std::deque<ErlNifBinary> m_deque;
};

/***** MACRO *****/
#define OK(env)  	enif_make_atom(env, "ok")
#define ERROR(env)	enif_make_atom(env, "error")

/***** EXPORT VARIABLE *****/

/***** PRIVATE VARIABLE *****/
static ErlNifResourceType* _ResType_DEQUE = NULL;

/***  Module Header  ******************************************************}}}*/
/**
* create fifo
* @par description
*   generate new fifo and resource object
*
* @return fifo handle
**/
/**************************************************************************{{{*/
ERL_NIF_TERM fifo_create(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
    DequeNifBinary* deque_ptr
        = new(enif_alloc_resource(_ResType_DEQUE, sizeof(DequeNifBinary))) DequeNifBinary;

    ERL_NIF_TERM term = enif_make_resource(env, deque_ptr);
    enif_release_resource(deque_ptr);

    return term;
}

/***  Module Header  ******************************************************}}}*/
/**
* peep front
* @par description
*   peep front item of fifo
*
* @return front item or error
**/
/**************************************************************************{{{*/
ERL_NIF_TERM fifo_front(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
    if (argc != 1) {
        return enif_make_badarg(env);
    }

    DequeNifBinary* deque_ptr;
    if (!enif_get_resource(env, argv[0], _ResType_DEQUE, (void**)&deque_ptr)) {
        return enif_make_badarg(env);
    }

    // mutual exclusion after here
    std::lock_guard<std::mutex> lock(deque_ptr->m_mutex);

    if (!deque_ptr->m_deque.empty()) {
		ErlNifBinary bin = deque_ptr->m_deque.front();

        ERL_NIF_TERM term_bin;
        enif_binary_to_term(env, bin.data, bin.size, &term_bin, 0);
        return enif_make_tuple2(env, OK(env), term_bin);
    }
    else {
        return enif_make_tuple2(env, ERROR(env), enif_make_string(env, "empty", ERL_NIF_LATIN1));
    }
}

/***  Module Header  ******************************************************}}}*/
/**
* pop item
* @par description
*   pop item from front of fifo
*
* @return poped item or error
**/
/**************************************************************************{{{*/
ERL_NIF_TERM fifo_pop(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
    if (argc != 1) {
        return enif_make_badarg(env);
    }

    DequeNifBinary* deque_ptr;
    if (!enif_get_resource(env, argv[0], _ResType_DEQUE, (void**)&deque_ptr)) {
        return enif_make_badarg(env);
    }

   // mutual exclusion after here
    std::lock_guard<std::mutex> lock(deque_ptr->m_mutex);

    if (!deque_ptr->m_deque.empty()) {
		ErlNifBinary bin = deque_ptr->m_deque.front();
		deque_ptr->m_deque.pop_front();

        ERL_NIF_TERM term_bin;
        enif_binary_to_term(env, bin.data, bin.size, &term_bin, 0);
        enif_release_binary(&bin);
        return enif_make_tuple2(env, OK(env), term_bin);
    }
    else {
        return enif_make_tuple2(env, ERROR(env), enif_make_string(env, "empty", ERL_NIF_LATIN1));
    }
}

/***  Module Header  ******************************************************}}}*/
/**
* push item
* @par description
*   push item into backend of fifo
**/
/**************************************************************************{{{*/
ERL_NIF_TERM fifo_push(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
    if (argc != 2) {
        return enif_make_badarg(env);
    }

    DequeNifBinary* deque_ptr;
    if (!enif_get_resource(env, argv[0], _ResType_DEQUE, (void**)&deque_ptr)) {
        return enif_make_badarg(env);
    }

    ErlNifBinary term_bin;
    if (!enif_term_to_binary(env, argv[1], &term_bin)) {
        return enif_make_badarg(env);
    }

    // mutual exclusion after here
    std::lock_guard<std::mutex> lock(deque_ptr->m_mutex);

    deque_ptr->m_deque.push_back(term_bin);
    return OK(env);
}

/***  Module Header  ******************************************************}}}*/
/**
* clear fifo
* @par description
*   delete all contents in fifo and reset fifo
**/
/**************************************************************************{{{*/
ERL_NIF_TERM fifo_clear(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
    if (argc != 1) {
        return enif_make_badarg(env);
    }

    DequeNifBinary* deque_ptr;
    if (!enif_get_resource(env, argv[0], _ResType_DEQUE, (void**)&deque_ptr)) {
        return enif_make_badarg(env);
    }

    // mutual exclusion after here
    std::lock_guard<std::mutex> lock(deque_ptr->m_mutex);

    if (!deque_ptr->m_deque.empty()) {
        std::for_each(deque_ptr->m_deque.begin(), deque_ptr->m_deque.end(),
            [](ErlNifBinary& bin){ enif_release_binary(&bin); });
        deque_ptr->m_deque.clear();
    }
    return OK(env);
}

/***  Module Header  ******************************************************}}}*/
/**
* size of fifo
* @par description
*   inquire number of contens in fifo
*
* @return size
**/
/**************************************************************************{{{*/
ERL_NIF_TERM fifo_size(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
    if (argc != 1) {
        return enif_make_badarg(env);
    }

    DequeNifBinary* deque_ptr;
    if (!enif_get_resource(env, argv[0], _ResType_DEQUE, (void**)&deque_ptr)) {
        return enif_make_badarg(env);
    }

    return enif_make_uint(env, deque_ptr->m_deque.size());
}

/***  Module Header  ******************************************************}}}*/
/**
* destroy fifo
* @par description
*   delete all contents in fifo and reset fifo
**/
/**************************************************************************{{{*/
void fifo_destroy(ErlNifEnv* env, void* ptr)
{
    DequeNifBinary* deque_ptr = reinterpret_cast<DequeNifBinary*>(ptr);

    if (!deque_ptr->m_deque.empty()) {
        std::for_each(deque_ptr->m_deque.begin(), deque_ptr->m_deque.end(),
            [](ErlNifBinary& bin){ enif_release_binary(&bin); });
    }

    deque_ptr->~DequeNifBinary();  // don't delete. you can destruct it only.
}

/***  Module Header  ******************************************************}}}*/
/**
* Nifs load
* @par description
*   Nifs initializer
**/
/**************************************************************************{{{*/
int load(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM load_info)
{
    _ResType_DEQUE = enif_open_resource_type(env, NULL, "mydeque", fifo_destroy, ERL_NIF_RT_CREATE, NULL);

    return 0;
}

// Let's define the array of ErlNifFunc beforehand: 
static ErlNifFunc nif_funcs[] = {
    // {erl_function_name, erl_function_arity, c_function, dirty_flags} 
    { "fifo_create",  0, fifo_create,    0 },
    { "fifo_front",   1, fifo_front,     0 },
    { "fifo_get",     1, fifo_pop,       0 },
    { "fifo_put",     2, fifo_push,      0 },
    { "fifo_clear",   1, fifo_clear,     0 },
    { "fifo_count",   1, fifo_size,      0 },
};

ERL_NIF_INIT(Elixir.FIFO, nif_funcs, load, NULL, NULL, NULL)

#転: Elixirインターフェイスの実装
NIFs/C++側の実装は終わった。Pythonの拡張モジュールの実装ならばココで終わりなのだが、Elixirではもう少し作業が必要だ。

さあ、Elixir側に目を視点を転じよう。

ElixirからNIFs関数をcallする為には、まずNIFsの入った動的ライブラリをロードしなきゃならない。そして、NIFs関数と同じ名前をもつSTUB関数が定義されていなければならな。STUB関数は、動的ライブラリがロードされるときに、NIFs関数でオーバーライドされるようだ。

その他諸々と併せて実装すると、こうなる。

fifo.ex
defmodule FIFO do
  @moduledoc """
  mutable FIFO data structure & operations
  """
  defstruct handle: nil
  
  def create() do
    %FIFO{handle: fifo_create()}
  end
  
  def front(%FIFO{handle: h}) do
    fifo_front(h)
  end

  def get(%FIFO{handle: h}) do
    fifo_get(h)
  end
  
  def put(%FIFO{handle: h}, x) do
    fifo_put(h, x)
  end
  
  def clear(%FIFO{handle: h}) do
    fifo_clear(h)                                                                                                                                                                                                     
  end
  
  def count(%FIFO{handle: h}) do
    fifo_count(h)
  end

  # loading NIF library
  @on_load :load_nif
  def load_nif do
    nif_file = Application.app_dir(:fifo, "priv/fifo_nif")
    :erlang.load_nif(nif_file, 0)
  end

  # stub implementations for NIFs (fallback)
  def fifo_create(),    do: raise "NIF fifo_create/0 not implemented"
  def fifo_front(_a),   do: raise "NIF fifo_front/1 not implemented"
  def fifo_get(_a),     do: raise "NIF fifo_get/1 not implemented"
  def fifo_put(_a,_b),  do: raise "NIF fifo_put/2 not implemented"
  def fifo_clear(_a),   do: raise "NIF fifo_clear/1 not implemented"
  def fifo_count(_a),   do: raise "NIF fifo_count/1 not implemented"
end

#結: 結果良ければ全て良し
ふぅ、一通り実装が終わった:sweat_smile:
では試してみよう。

> iex -S mix
Erlang/OTP 22 [erts-10.4.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe]

mkdir -p /home/shozo/fifo/_build/dev/lib/fifo/priv
mkdir -p /home/shozo/fifo/_build/dev/lib/fifo/obj
g++ -c -I/home/shozo/.asdf/installs/erlang/22.0.4/usr/include -O2 -Wall -Wextra -Wno-unused-parameter -pedantic -fPIC -o /home/shozo/fifo/_build/dev/lib/fifo/obj/fifo_nif.o src/fifo_nif.cc
g++ -o /home/shozo/fifo/_build/dev/lib/fifo/priv/fifo_nif.so -L/home/shozo/.asdf/installs/erlang/22.0.4/usr/lib -fPIC -shared /home/shozo/fifo/_build/dev/lib/fifo/obj/fifo_nif.o
Compiling 1 file (.ex)
Generated fifo app

Interactive Elixir (1.9.0) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> fifo = FIFO.create
%FIFO{handle: #Reference<0.2846310002.3727818754.139300>}
iex(2)> FIFO.front(fifo)
{:error, 'empty'}
iex(3)> FIFO.put(fifo, ["Hello", :world])
:ok
iex(4)> FIFO.put(fifo, 2.7182)
:ok
iex(5)> FIFO.count(fifo)
2
iex(6)> FIFO.get(fifo)
{:ok, ["Hello", :world]}
iex(7)> FIFO.get(fifo)
{:ok, 2.7182}
iex(8)> 

良さそうだな。仕様通りに動いているようだ。

#ふりかえり
関数型言語にあるまじき異端な拡張モジュール"mutableなFIFO"を実装してみた。

immutableで安全な楽園から一歩外のNIFsの世界に踏み込むと、しごく日常的で危険な世界が広がっていた。脛に多くの傷をもつ経験と勘を頼りに、安全に心掛けたNIFsの実装を行ったつもりであるが、果たしてどうだか分らない。

う~む、極論すれば入力されたデータを加工してDBに放り込むだけの、関数型言語がしっくりとfitするWEB系が羨ましいなぁ。

尚、ここに至るまでは、多くの先人たちの投稿記事を参考にさせて頂いた。感謝m(_ _)m

#参考文献
[1] Erlang Run-Time System Application (ERTS) Reference Manual Version 10.4 / erl_nif
[2] "Using C from Elixir with NIFs" Andrea Leopardi
[3] "Elixir Native Interoperability – Ports vs. NIFs" TONY BAKER
[4] GitHub: "Elixir Circuits - GPIO"
[5] OTP Design Principles User's Guide Version 10.4 / 7.4 Directory Structure

10
6
3

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
10
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?