RustでNode.jsのネイティブモジュールが作れるやつを試した

# 背景

 <blockquote class="twitter-tweet" lang="ja"><p lang="ja" dir="ltr">Rust で node の native modules を書くの、Rustを書いてみたいからっていう理由だけで試してみたい。 <a href="https://t.co/vek4YFjW5e">https://t.co/vek4YFjW5e</a> <a href="https://t.co/uqpd95C4QQ">https://t.co/uqpd95C4QQ</a></p>&mdash; Yosuke FURUKAWA (@yosuke_furukawa) <a href="https://twitter.com/yosuke_furukawa/status/679861606491095040">2015, 12月 24</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

なんて、ツイートをたまたま見かけ、

<blockquote class="twitter-tweet" lang="ja"><p lang="ja" dir="ltr">原理的にちょっとまえのRustだとdleopn時に呼び出す関数を指定できなかったから直接.nodeファイルはRustが吐けないはずなんだけど、その辺りどうなってるのだろうか / “dherman/wc-demo · GitHub” <a href="https://t.co/pdAqgRMFbW">https://t.co/pdAqgRMFbW</a></p>&mdash; kjunichi (@kjunichi) <a href="https://twitter.com/kjunichi/status/679871965188890625">2015, 12月 24</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

# なぜ原理的に無理そうなのか

- http://abrakatabura.hatenablog.com/entry/2015/12/14/214922
- https://users.rust-lang.org/t/library-onload-initialization/3079/2

# やってみた

Rustがある時から、「__attribute__((constructor))」をサポートしたのかもと思い、調べるも見当たらず。

wc-demoのコードをたどり、依存モジュールのリポジトリのpackage.jsonをたどるも
node-ffiすら見当たらず、これは俺が知らない未知のテクノロジーを使って解決したのか！

と、期待を膨らませて実際に動かしてみた。

## rustのアップデート

1.5以上とwc-demoに書いてあるので、

```
curl -sSf https://static.rust-lang.org/rustup.sh | sh
```

OSXではPkgも用意されているので、そこらへんはお好みで。

## wc-demoをクローンして試す

```shell-session
$ nodebrew ls
v0.10.35
v0.12.0
v4.2.1
v5.1.1
v5.2.0
v5.3.0

current: v5.3.0

$ npm install
npm WARN prefer global node-gyp@3.0.3 should be installed with -g

> wc-demo@0.0.0 postinstall /Users/kjw_junichi/work/rust/neon/wc-demo
> neon build

  ACTION binding_gyp_wc_demo_target_generate Release/obj.target/wc_demo/geni/binding.cc
  ACTION binding_gyp_wc_demo_target_cargo ../target/release/libwc_demo.a
Unable to update file:///Users/kjw_junichi/work/rust/neon/wc-demo

Caused by:
  Could not find `Cargo.toml` in `/Users/kjunichi/work/rust/neon/neon`
make: *** [../target/release/libwc_demo.a] Error 101
gyp ERR! build error 
gyp ERR! stack Error: `make` failed with exit code: 2
gyp ERR! stack     at ChildProcess.onExit (/Users/kjunichi/work/rust/neon/wc-demo/node_modules/node-gyp/lib/build.js:270:23)
gyp ERR! stack     at emitTwo (events.js:87:13)
gyp ERR! stack     at ChildProcess.emit (events.js:172:7)
gyp ERR! stack     at Process.ChildProcess._handle.onexit (internal/child_process.js:200:12)
gyp ERR! System Darwin 15.2.0
gyp ERR! command "/Users/kjunichi/.nodebrew/node/v5.3.0/bin/node" "/Users/kjunichi/work/rust/neon/wc-demo/node_modules/.bin/node-gyp" "rebuild"
gyp ERR! cwd /Users/kjunichi/work/rust/neon/wc-demo
gyp ERR! node -v v5.3.0
gyp ERR! node-gyp -v v3.0.3
gyp ERR! not ok 

npm ERR! Darwin 15.2.0
npm ERR! argv "/Users/kjunichi/.nodebrew/node/v5.3.0/bin/node" "/Users/kjunichi/.nodebrew/current/bin/npm" "install"
npm ERR! node v5.3.0
npm ERR! npm  v3.3.12
npm ERR! code ELIFECYCLE
npm ERR! wc-demo@0.0.0 postinstall: `neon build`
npm ERR! Exit status 1
npm ERR! 
npm ERR! Failed at the wc-demo@0.0.0 postinstall script 'neon build'.
npm ERR! Make sure you have the latest version of node.js and npm installed.
npm ERR! If you do, this is most likely a problem with the wc-demo package,
npm ERR! not with npm itself.
npm ERR! Tell the author that this fails on your system:
npm ERR!     neon build
npm ERR! You can get their info via:
npm ERR!     npm owner ls wc-demo
npm ERR! There is likely additional logging output above.

npm ERR! Please include the following file with any support request:
npm ERR!     /Users/kjunichi/work/rust/neon/wc-demo/npm-debug.log
```

## 動かすには

```bash
cd ..
git clone https://github.com/dherman/neon.git
npm install -g neon-cli
cd wc-demo
cargo update
npm install
```

```shell-session
$ node -e 'require("./")'
node -e 'require("./")'
3092 [280ms, 914μs, 751ns]
3092 [40ms, 930μs, 825ns]
```

動きました！

```shell-session
$ cat binding.gyp
```

```json
# THIS FILE WAS AUTOMATICALLY GENERATED. DO NOT EDIT.

{
    "target_defaults": {
        "default_configuration": "Release"
    },

    "targets": [{
        "target_name": "wc_demo",

        "variables": {
            "rust_inputs": ["src/lib.rs"],
            "static_lib": "target/release/<(STATIC_LIB_PREFIX)wc_demo<(STATIC_LIB_SUFFIX)",
            "shared_lib": "target/release/<(SHARED_LIB_PREFIX)wc_demo<(SHARED_LIB_SUFFIX)"
        },

        "sources": ["<(INTERMEDIATE_DIR)/binding.cc"],

        "include_dirs": ["<!(neon include-path)"],

        "libraries": ["../<(shared_lib)"],

        "actions": [{
            "action_name": "cargo",
            "inputs": ["<@(rust_inputs)"],
            "outputs": ["../<(static_lib)"],
            "action": ["cargo", "rustc", "--release", "--", "--crate-type", "staticlib"]
        }, {
            "action_name": "shared_lib",
            "inputs": ["<(static_lib)"],
            "outputs": ["../<(shared_lib)"],
            # FIXME: OSX-specific
            "action": ["gcc", "-dynamiclib", "-Wl,-undefined,dynamic_lookup", "-Wl,-force_load,<(static_lib)", "-o", "<(shared_lib)"]
        }, {
            "action_name": "generate",
            "inputs": [],
            "outputs": ["<(INTERMEDIATE_DIR)/binding.cc"],
            "action": ["neon", "generate", "<(INTERMEDIATE_DIR)/binding.cc"]
        }]
    }]

}
```

ということで、biding.ccをエントリポイントにして、その先がRustで記述されたモジュールとなっているようです。

# 関連

- [Rustでコマンドラインアプリでキーが押されたかの判定しつつ、別の処理も進めるには ](https://abrakatabura.hatenablog.com/entry/2017/09/20/065024)
- [RustでWebカメラの映像をコマンドプロンプトに出すコマンドを作った](https://abrakatabura.hatenablog.com/entry/2017/09/21/064630)
- [RustでGoのライブラリを使うときのCargoのbuild.rsの書き方](https://abrakatabura.hatenablog.com/entry/2017/10/22/110223)
- [Surface Goのカメラ映像をRustでコマンドプロンプトに表示した](https://abrakatabura.hatenablog.com/entry/2018/12/20/000602)
- [Rustで共有ライブラリを作る際に必要だったCのconst char*をRustの&strにする方法](https://abrakatabura.hatenablog.com/entry/2019/02/02/095326)