wasm-bindgen で Rust から JS へ JSON を渡す

本来の wasm は float しか送れないが、wasm-bindgen を経由すれば自分でTextEncoderを実装することなく文字列の受け渡しができる。

コンパイルやインストールは略。

```toml:Cargo.toml
[dependencies]
wasm-bindgen = "0.2"
serde = "1.0"
serde_json = "1.0"
serde_derive = "1.0"

[lib]
crate-type = ["cdylib"]
```

`#[wasm-bindgen]` で `&str` で受け取り、`String` を返す。
今回は serde を使って struct を JSONに変換して返す。


```rust:src/lib.rs
#[macro_use]
extern crate wasm_bindgen;

#[macro_use]
extern crate serde_derive;
extern crate serde;
extern crate serde_json;

#[derive(Serialize)]
pub struct MyObj {
    text: String,
    x: i32,
    y: i32,
}

#[wasm_bindgen]
pub fn xxx(x: &str) -> String {
    let myobj = MyObj {
        text: x.to_string(),
        x: 1,
        y: 2,
    };
    let serilized = serde_json::to_string(&myobj).unwrap();
    serilized
}
```

JS側で文字列を受け取ってJSON.parseする。

```js
const main = async () => {
  const { xxx } = await import("../gen/rust_wasm");

  const arr = new Array(1000).fill(0);
  console.time("xxx");
  arr.forEach(() => {
    console.log(JSON.parse(xxx("foo")));
  });
  console.timeEnd("xxx");
};

main();
```


1000回実行して 300ms 程度。あんまりパフォーマンスが良いとは言い難い。

追記: console.log を考慮するのを忘れていた。抜くと 1000回で 6ms。 十分速い。

みっちりチューニングするなら、当然自分でABIのような何かを決めてデコードした方が速い。

ちなみに回数を増やすと線形に増加するので、内部でJITが効いたりはしてない模様。

## おまけ: 生成されてるコード

TextEncoder を自分で使うとだるいという話

```js

const TextEncoder = typeof self === 'object' && self.TextEncoder
    ? self.TextEncoder
    : require('util').TextEncoder;

let cachedEncoder = new TextEncoder('utf-8');

let cachegetUint8Memory = null;
function getUint8Memory() {
    if (cachegetUint8Memory === null || cachegetUint8Memory.buffer !== wasm.memory.buffer) {
        cachegetUint8Memory = new Uint8Array(wasm.memory.buffer);
    }
    return cachegetUint8Memory;
}

function passStringToWasm(arg) {
    
    const buf = cachedEncoder.encode(arg);
    const ptr = wasm.__wbindgen_malloc(buf.length);
    getUint8Memory().set(buf, ptr);
    return [ptr, buf.length];
}

const TextDecoder = typeof self === 'object' && self.TextDecoder
    ? self.TextDecoder
    : require('util').TextDecoder;

let cachedDecoder = new TextDecoder('utf-8');

function getStringFromWasm(ptr, len) {
    return cachedDecoder.decode(getUint8Memory().subarray(ptr, ptr + len));
}

let cachedGlobalArgumentPtr = null;
function globalArgumentPtr() {
    if (cachedGlobalArgumentPtr === null) {
        cachedGlobalArgumentPtr = wasm.__wbindgen_global_argument_ptr();
    }
    return cachedGlobalArgumentPtr;
}

let cachegetUint32Memory = null;
function getUint32Memory() {
    if (cachegetUint32Memory === null || cachegetUint32Memory.buffer !== wasm.memory.buffer) {
        cachegetUint32Memory = new Uint32Array(wasm.memory.buffer);
    }
    return cachegetUint32Memory;
}
/**
* @param {string} arg0
* @returns {string}
*/
export function xxx(arg0) {
    const [ptr0, len0] = passStringToWasm(arg0);
    const retptr = globalArgumentPtr();
    try {
        wasm.xxx(retptr, ptr0, len0);
        const mem = getUint32Memory();
        const rustptr = mem[retptr / 4];
        const rustlen = mem[retptr / 4 + 1];
        
        const realRet = getStringFromWasm(rustptr, rustlen).slice();
        wasm.__wbindgen_free(rustptr, rustlen * 1);
        return realRet;
        
        
    } finally {
        wasm.__wbindgen_free(ptr0, len0 * 1);
        
    }
    
}
```


## 追記

wasm-bindgen に serde-serialized という feature があった

https://rustwasm.github.io/wasm-bindgen/reference/arbitrary-data-with-serde.html

```toml:Cargo.toml
[dependencies]
serde = "1.0"
serde_json = "1.0"
serde_derive = "1.0"

[dependencies.wasm-bindgen]
version = "^0.2"
features = ["serde-serialize"]
```

```rust
#[wasm_bindgen]
pub fn xxx(x: &str) -> JsValue {
    let myobj = MyObj {
        text: x.to_string(),
        x: 1,
        y: 2,
    };
    JsValue::from_serde(&myobj).unwrap()
}
```

JsValue型で、型もクソもないが、 JS側でJSON.parse しないで済む分使いやすい