🧊

JSからRustのコードを実行し、RustからもJSのコードを実行する

それが簡単にできちゃう、napi-rsなら。

napi-rs/napi-rs: A framework for building compiled Node.js add-ons in Rust via Node-API https://github.com/napi-rs/napi-rs

そしてこれを使って、oxlintはJSで書かれたESLintプラグインを実行してる。

Node-API

Node-API | Node.js v25.0.0 Documentation https://nodejs.org/api/n-api.html

Node.jsのコードから、C/C++やらで書いたコードを呼べるやつ。

で、Rustでもそれ用のコードをいい感じに書けるようになるのが、napi, napi_derive crateと、それをよしなにビルドしてくれる@napi-rs/cliというわけ。

JSからRust

これは本当に簡単で、こういう関数を用意するだけ。

use napi_derive::napi;

#[napi]
pub fn hello(name: String) -> String {
    format!("🦀 < `Hello, {name}!`")
}

そしてこれをビルドすると、

  • 指定したプラットフォーム向けにコンパイルされた.node
  • それを読み込んで関数をexportする.js.d.ts

というファイルたちができるので、それを使う。

import { hello } from "./index.js";

console.log(hello("World"));

ちなみに、WASM向けのビルドにも対応してるので、ブラウザでも動かせるコードも出力できる。

WebAssembly – NAPI-RS https://napi.rs/docs/concepts/webassembly

すばらしい。

RustからJS

こっちが本題。

RustからJSを呼ぼうとすると、最初に思いつくのは、

  • RustでメインのCLIを用意
  • 子プロセスとしてNode.jsをspawn
  • プロセス間でメッセージング

ってなりがち。

しかし、ついさっき書いたように、今やRustで書いたCLIは、JSで動かせることがわかってる。

なら、JSで書いた関数を、そのままコールバック的に渡せば・・・?っていう話。

実行イメージはこう。

import { helloWithCallback } from "./index.js";

await helloWithCallback((arg) =>
  arg
    .replace("🦀", "👻")
    .replace("{{template}}", "THIS IS JS~")
    .toUpperCase(),
);

そしてそれを実現するコードがこれ。

use napi::bindgen_prelude::{Function, Result};
use napi_derive::napi;

#[napi]
pub fn hello_with_callback(callback: Function<String, String>) -> Result<String> {
    let result = callback.call("🦀 < `Hello, {{template}}!`".to_string())?;
    println!("{result}");
    Ok(result)
}

特筆すべき点としては、

  • 末端で実行されるのはNode.jsだが、本体はRustで書ける
  • そのRustのコードでは、先と同様#[napi]をつける
  • 引数でFunction型のコールバックを受け取って、それをよきときに呼ぶ
    • RustからJSに引数も渡せる

というあたり。

これが最もシンプルな同期で呼ぶパターンで、ここに追加の要件としてで、

  • 引数を複数渡して呼びたい
  • Rust側で定義した別スレッドで呼びたい
  • Promiseを返すJSを呼びたい
  • ノンブロッキングで呼びたい

などなどの要望が出てくるので、それに応じて使うAPIを変えていくことになる・・・らしい。

Functions and Callbacks in NAPI-RS – NAPI-RS https://napi.rs/blog/function-and-callbacks

いや〜、すごいっす。

leaysgur/js-from-rust-example: Call JS function callback from Rust! https://github.com/leaysgur/js-from-rust-example

公式のサンプル集も見つけた。

napi-rs/callback-example: Example for the blog post https://napi.rs/blog/function-and-callbacks https://github.com/napi-rs/callback-example