Edited at

Rust と Node.js の文字列やり取り、そして Callback


環境

rustc 1.32.0 (9fda7c223 2019-01-16)

Node.js 10.15.1


Rust から Node.js に Callback したい

というわけで、Cargo.toml です。cdylibってやると動的ライブラリができます。Windows では dll ってやつですね。


Cargo.toml

[package]

name = "callback"
version = "0.1.0"
authors = ["benki"]
edition = "2018"

[dependencies]

[lib]
name = "callback"
crate-type = ["cdylib"]


続いて、lib.rs です。rust_funcっていう関数は第一引数に C の文字列へのポインタ、第二引数にコールバック関数へのポインタを受け取るようになってます。

rust_func関数の1行目で Node.js の世界からやってきた C 文字列へのポインタを Rust の世界の文字列に変換して、2行目でdbg!マクロでその内容を表示しています。

その後の処理はカレントディレクトリにあるファイルのパスをコールバック関数の引数に渡しています。Rust の世界の文字列は NULL 終端文字がないので、そのまま Node.js の世界に渡すと結果がおかしなことになります。なので、path.push('\0');で NULL 終端文字を追加する必要があります。(もうちょっとうまいことできる方法があったら教えてください)なので、std::ffi::CStringで C の世界の文字列に変換してます。(tatsuya6502さんにコメントで教えてもらいました)


lib.rs

use std::fs;

use std::os::raw::c_char;
use std::ffi::{CStr, CString};

type CallBack = fn(*const c_char);

#[no_mangle]
pub fn rust_func(string: *const c_char, cb: CallBack) {
let str_from_node = unsafe { CStr::from_ptr(string).to_str().unwrap() };
dbg!(str_from_node);

fs::read_dir(".").unwrap()
.for_each(|r| {
if let Ok(r) = r {
// let mut path = r.path().to_string_lossy().to_owned().to_string();
// path.push('\0');
// cb(path.as_ptr() as *const c_char);
if let Ok(path) = CString::new(r.path().to_string_lossy().as_bytes()) {
cb(path.as_ptr() as *const c_char);
}
}
});
}


最後に Node.js 側のコードです。


index.js

let ffi = require("ffi");

let util = require("util");

let lib = ffi.Library("<path to callback.dll or so>",
{"rust_func": // Rust の世界の関数の名前
["void", // 戻り値
["string", // 第一引数
"pointer"] // 第二引数(コールバック関数へのポインタ)
]
}
);

let callback = ffi.Callback("void", ["string"], (path) => {
console.log(path);
});

let rust_func = util.promisify(lib.rust_func.async);

(async () => {
await rust_func("Hello from Node!", callback);
})().catch(e => {
console.log(e);
});