【Rust×WebAssembly】Rust JavaScript間でデータをやり取りする
概要
- WebAssemblyを使ってJavaScriptからRustの関数を使うときの話
- JavaScript<->Rustのデータのやり取りをする方法をまとめました。
- 数値、String、Arrayで書き方が違うみたいです。
参考にしたページ
- https://sbfl.net/blog/2017/03/13/rust-wasm/
- 全体的なWebAssemblyの動かし方について参考にさせていただきました。
- http://stackoverflow.com/questions/24145823/rust-ffi-c-string-handling
- Stringの受け渡しについて参考にさせていただきました。
- http://qiita.com/shizu-oka/items/d386001b45f5cd2aacab
- Arrayの受け渡しについて参考にさせていただきました。
Rust, Webassemblyとは
省略
必要なもの
- RustをWebAssemblyにコンパイルできる環境(Rust>=1.17.0)
- こちらのブログが非常に参考になります。
- https://sbfl.net/blog/2017/03/13/rust-wasm/
- Google Chrome等のWebAssemblyが動かせる環境
- Web Server的なもの
データやり取りの方法
共通
src/main.rs
をwasm/wasm_test.wasm
,wasm/wasm_test.js
にコンパイルします。index.html
でwasm/wasm_test.wasm
をロードしたらsrc/main.js
が実行されます。src/main.js
から、src/main.rs
で実装したrustの関数を呼び出します。
index.html
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>wasm test</title> <script> var Module = {}; fetch('wasm/wasm_test.wasm') .then((response) => response.arrayBuffer()) .then((wasm) => { Module.wasmBinary = wasm; const scriptElem = document.createElement('script'); scriptElem.src = 'wasm/wasm_test.js'; scriptElem.addEventListener('load', (e) => { const mainScriptElem = document.createElement('script'); mainScriptElem.src = 'src/main.js'; document.body.appendChild(mainScriptElem); }); document.body.appendChild(scriptElem); }); </script> </head> <body> <div id="number"></div> <div id="string"></div> <div id="array"></div> </body> </html>
想定する結果
- JavaScriptからRustに渡された値は
println!
マクロによってconsole.log
に出力します。 - RustからJavaScriptに渡された値は
div
に出力されます。 - githubのコードを動かすと↓のようになります。
数値の受け渡し
数値(js<->rust)
数値を受け渡す場合は、JavaScript、Rustともに普通に関数を書けば良いです。
i32
やf64
等数値っぽい型はそのまま渡せますが、usize
なんかは渡せませんでした。
src/main.js
// number: js <-> rust const wasm_number = Module.cwrap('wasm_number', 'number', ['number', 'number']); document.getElementById('number').innerText = wasm_number(3, 4.2);
src/main.rs
#[no_mangle] pub extern "C" fn wasm_number(n: i32, m: f64) -> f64 { m + (n as f64) * 2.0 }
文字列の受け渡し
文字列(js->rust)
JavaScriptの文字列はRustで*c_char
型として受け取ります。
これをRustのString型に変換する必要があります。
src/main.js
const wasm_string_to_rust = Module.cwrap('wasm_string_to_rust', null, ['string']); wasm_string_to_rust("string from js");
src/main.rs
use std::os::raw::c_char; use std::ffi::CStr; use std::str; #[no_mangle] pub extern "C" fn wasm_string_to_rust(c_buf: *const c_char) { let c_str: &CStr = unsafe { CStr::from_ptr(c_buf) }; let buffer: &[u8] = c_str.to_bytes(); let rust_str: &str = str::from_utf8(buffer).unwrap(); let rust_string: String = rust_str.to_owned(); println!("{}", rust_string); }
文字列(rust->js)
rustの文字列をjsに渡すためには、同様に*c_char
型で返せば良いですが、
Rustの関数内で作った文字列のアドレスをas_ptr
で返してしまうと、
関数から抜けたときに文字列のメモリが開放されてしまいます。
今回は、lazy_static
を利用してstaticなStringを保持して、
勝手にメモリが開放されないようにしています。
JavaScriptから関数を呼ぶ場合はこの処理をなくしても動作するそうですが、 Rustから関数を呼ぶとメモリ開放されて値を受け取れないので、念のためやっておいた方が良いと思います。
src/main.js
const wasm_string_to_js = Module.cwrap('wasm_string_to_js', 'string', []); document.getElementById('string').innerText = wasm_string_to_js();
src/main.rs
lazy_static! { static ref HEAP_STRING: Mutex<String> = Mutex::new(String::new()); } #[no_mangle] pub extern "C" fn wasm_string_to_js() -> *const c_char { let s: String = "string from rust".to_string(); let mut output = HEAP_STRING.lock().unwrap(); output.clear(); write!(output, "{}\0", s).unwrap(); output.as_ptr() as *const c_char }
配列の受け渡し
配列(js->rust)
配列の受け渡しをする時もメモリ管理は重要です。
JavaScriptの方で配列用のメモリを確保し、Rust側でその値を*f32
として受け取ります。
JavaScript側で普通にnew FloatArray(5)
みたいな感じで配列を作っちゃうと動きません。
src/main.js
// array: js -> rust const wasm_array_to_rust = Module.cwrap('wasm_array_to_rust', null, ['number', 'number']); var len = 5; var bufsize = len * 4; var bufptr = Module._malloc(bufsize); Module._memset(bufptr, 0, bufsize) var buf = new Float32Array(Module.HEAPF32.buffer, bufptr, len); for (var i = 0; i < len;i ++){ buf[i] = 1.1 * i; } wasm_array_to_rust(len, buf.byteOffset); Module._free(bufptr);
src/main.rs
#[no_mangle] pub extern "C" fn wasm_array_to_rust(_len: i32, ptr: *const f32) { let len = _len as usize; let slice: &[f32] = unsafe { std::slice::from_raw_parts(ptr, len) }; for n in slice { println!("{}", n); } }
配列(rust->js)
RustからJavaScriptに配列を渡すときも、配列自体はJavaScriptで確保して、Rust側で値を埋めるようにします。
※Rustが1.16.0だとうまく行かなかったので、動かないときはRustのバージョンを上げるとよいかもです。
src/main.js
// array: rust -> js const wasm_array_to_js = Module.cwrap('wasm_array_to_js', null, ['number', 'number']); var len = 6; var bufsize = len * 4; var bufptr = Module._malloc(bufsize); Module._memset(bufptr, 0, bufsize) var buf = new Float32Array(Module.HEAPF32.buffer, bufptr, len); wasm_array_to_js(len, buf.byteOffset); document.getElementById('array').innerText = buf; Module._free(bufptr);
src/main.rs
#[no_mangle] pub extern "C" fn wasm_array_to_js(_len: i32, ptr: *mut f32) { let len = _len as usize; let mut v: Vec<f32> = vec![0.5; len]; v[3] = 99.3; let mut res: &mut [f32] = unsafe { std::slice::from_raw_parts_mut(ptr, len) }; res.clone_from_slice(&v.as_slice()) }
感想
就活生にWebAssemblyについて質問されたが、うまく答えられなかったのでWebAssembly勉強したい。- Rust×WebAssemblyで遊ぼうと思ったが、ここでかなり詰まってしまった。
- せっかくJavaScriptとRust使っているのにCみたいにメモリ管理するの辛い。
- ここを乗り切れば平和な世界が訪れる?
- JavaScriptもRustも超初心者なので何か間違ってる気がする