Rust -> wasm -> browser & nodejs
Tags: rust, javascript, browser, node
Published: 2019-08-14 10:03:30

When learning rust I found de rust-wasm ["Hello World"](https://rustwasm.github.io/book/game-of-life/hello-world.html) example a little overwhelming (+ 350 npm packages) and with many extra tools (wasm-pack, cargo-generate, typescript and webpack). This is my effort in describing a trial and error process in how to build and use wasm with rust, why and how to use ` wasm-bindgen`, with only the minimal requirements from scratch.   


### Starting 

Creating a rust lib
```shell
cargo new libexample --lib

cd libexample
``` 

Building this lib for wasm is done by adding the target to cargo build command
```shell
cargo build --target wasm32-unknown-unknown --release
```
However it does not do anything yet, it is a starting point.
Open src/lib.rs and lets add 2 functions.
```rust
#[no_mangle]
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

// this function changes a value by reference (borrowing and change)
#[no_mangle]
pub fn alter(a: &mut [u8]) {
    a[1] = 12;
}
```
Now lets build the lib again. We somehow need to generate .wasm files.
Execute the build command and inspect 

```shell
ls target/wasm32-unknown-unknown/release/deps
```
We need to indicate that we want a dynamic lib [crate-type](https://doc.rust-lang.org/reference/linkage.html). Open Cargo.toml and add the following.
```toml
[lib]
crate-type = ["cdylib"]

```
Now `re-build` and copy the *.wasm file
```
cargo build --target wasm32-unknown-unknown --release
mkdir example
cp target/wasm32-unknown-unknown/release/deps/libexample.wasm example/.
```
We 'r gonna use this wasm file first in nodejs. Now create this script: example/nodeTest.js
```javascript
const fs = require('fs');

const wasmBin = fs.readFileSync(__dirname + '/libexample.wasm');
const wasmModule = new WebAssembly.Module(wasmBin);
const libexample = new WebAssembly.Instance(wasmModule, []);

// Call wasm method 'add' typical stack method
let result = libexample.exports.add(10, 2);
console.log('add result:' + result);
// Now let's access heap memory /reference
// RuntimeError: unreachable
var a = new Uint8Array(100);
a[0] = 225;
a[1] = 10;
console.log('pre remote call a[1] === ' + a[1]);
libexample.exports.alter(a);
console.log('post remote call a[1] === ' + a[1]);

```
Run this script with node:
```
node example/nodeTest.js
```
Now it is getting interesting: the simple 'stack' type vars used in the 'add' method work like a charm ('add result'). It is not possible to change the value in the Uint8Array (heap memory) in this scenario. Therefore we need [some extra steps](https://hacks.mozilla.org/2018/04/javascript-to-rust-and-back-again-a-wasm-bindgen-tale/) this is where `wasm_bindgen` comes to play. Open `Cargo.toml` and add the following line:
```
[dependencies]
wasm-bindgen = "0.2"
```
Open `src/lib.rs` and change it like this:
```rust
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

// this function changes a value by reference (borrowing and change)
#[wasm_bindgen]
pub fn alter(a: &mut [u8]) {
    a[1] = 12;
}
```
Now rebuild and copy the wasm-file
```shell
cargo build --target wasm32-unknown-unknown --release && cp target/wasm32-unknown-unknown/release/deps/libexample.wasm example/.
```
Exciting does nodejs run as expected?
```shell
node example/nodeTest.js

```
Probably not, wasm_bindgen changes the workflow quite a bit: 
```shell
TypeError: WebAssembly.Instance(): Import #0 module="__wbindgen_placeholder__" error: module is not an object or function
```
Now, we need some extra steps: `wasm-bindgen-cli`
```shell
cargo install wasm-bindgen-cli
```
Takes some time with a lot of Compiling (some 181 crates)
`Installed package `wasm-bindgen-cli v0.2.48` (executables `wasm-bindgen`, `wasm-bindgen-test-runner`, `wasm2es6js`)`
Time to see what is does:
```shell
wasm-bindgen target/wasm32-unknown-unknown/release/deps/libexample.wasm  --out-dir ./example
```
Wow, when looking in the `example` folder there are `typescript` @types and a ready to use libexample.js file. So this creates the javascript wrapper for us (ready to use?). Now lets change our `example/nodeTest.js` accordingly.
```javascript
const libexample = require('./libexample.js');

// Call wasm method 'add' typical stack method
let result = libexample.add(10, 2);
console.log('add result:' + result);
// Now let's access heap memory /reference
var a = new Uint8Array(100);
a[0] = 225;
a[1] = 10;
console.log('pre remote call a[1] === ' + a[1]);
libexample.alter(a);
console.log('post remote call a[1] === ' + a[1]);
```
Lets run:
```shell
node example/nodeTest.js
```
Bad luck : nodejs doesn't allow `import * from ..` yet. We need to do something extra for nodejs. Add the `--nodejs` flag.
```shell
wasm-bindgen target/wasm32-unknown-unknown/release/deps/libexample.wasm --nodejs  --out-dir ./example
```
Now run :
```shell
node example/nodeTest.js
```
Yeah, the two methods are working as expected. Now lets create a browser integration. Create a folder www.
```shell
mkdir www
```
Run the following command the create te javascript wrapper for browser-based integration.

```shell
wasm-bindgen target/wasm32-unknown-unknown/release/deps/libexample.wasm --out-dir ./www
```
Create an `www/index.html` file like this.
```html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Trial and error</title>
  </head>
  <body>
    <script src="./index.js"></script>
  </body>
</html>
```

Create the `www/index.js` file like this:
```javascript
import * as libexample from "libexample"
// Call wasm method 'add' typical stack method
let result = libexample.add(10, 2);
console.log('add result:' + result);
// Now let's access heap memory /reference
var a = new Uint8Array(100);
a[0] = 225;
a[1] = 10;
console.log('pre remote call a[1] === ' + a[1]);
libexample.alter(a);
console.log('post remote call a[1] === ' + a[1]);

```
Now serve de`www` folder via a/any [http-server](https://www.npmjs.com/package/http-server). and inspect the console output.
Stil no luck. Somehow there is a network error message `disallowed MIME type (“application/wasm”)`. Wasm does not run this way in a browser via `import * as example from 'file.wasm'`. So I landed on [webassembly.org](https://webassembly.org/getting-started/js-api/) and there it's very clear. You cannot load wasm via an import statement. We need to:

1. Get the .wasm bytes in a ArrayBuffer.
2. Compile the bytes into a WebAssembly.Module
3. Instantiate the WebAssembly.Module with imports to get the callable exports
  
Change the generated`www/libexample.js` file to this:
```javascript
var libexample;
fetch('libexample_bg.wasm').then(response => {
  response.arrayBuffer().then(bytes => {
    var wasmModule = new WebAssembly.Module(bytes);
    libexample = new WebAssembly.Instance(wasmModule, []).exports;
  })
});
/**
* @param {number} a
* @param {number} b
* @returns {number}
*/
export function add(a, b) {
    const ret = libexample.add(a, b);
    return ret;
}

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

let WASM_VECTOR_LEN = 0;

function passArray8ToWasm(arg) {
    const ptr = libexample.__wbindgen_malloc(arg.length * 1);
    getUint8Memory().set(arg, ptr / 1);
    WASM_VECTOR_LEN = arg.length;
    return ptr;
}
/**
* @param {Uint8Array} a
*/
export function alter(a) {
    const ptr0 = passArray8ToWasm(a);
    const len0 = WASM_VECTOR_LEN;
    try {
        libexample.alter(ptr0, len0);
    } finally {
        a.set(getUint8Memory().subarray(ptr0 / 1, ptr0 / 1 + len0));
        libexample.__wbindgen_free(ptr0, len0 * 1);
    }
}
```
The wasm file is now loaded as suggested and we needed to change the bindings here and there. There is one drawback. it is possible that `libexample` module methods are called while the wasm module is not loaded. In production we do need to throw some event when the module is ready to use. For now we use a timeout, Change www/index.js to this:

```javascript
import * as libexample from "./libexample.js"
setTimeout( () => {
  // Call wasm method 'add' typical stack method
  let result = libexample.add(10, 2);
  console.log('add result:' + result);
  // Now let's access heap memory /reference
  var a = new Uint8Array(100);
  a[0] = 225;
  a[1] = 10;
  console.log('pre remote call a[1] === ' + a[1]);
  libexample.alter(a);
  console.log('post remote call a[1] === ' + a[1]);
}, 1000);

```
Now this runs and that's okay.
 But of course things can be simpler by just use the proper commands.
wasm-bindgen with `--target web`
```shell
wasm-bindgen target/wasm32-unknown-unknown/release/deps/libexample.wasm --target web  --out-dir ./www
```
This will change the libexample.js to a ready to use script.

### Conclusion

When sharing heap memory (or access js from rust) wasm-bindgen is very handy/essential. It also generates the required embed code (for nodejs and the web). However, when developing a library I would use an extra wasm-bindgen glue library in the last stage of the project and keep the actual code/lib as standard and simple as possible.