Rust and WebAssembly for the masses - wasm-bindgen
Tags: rust, javascript, webassembly, webdev
Published: 2020-01-02 20:11:48

# Binding your WebAssembly and JavaScript with wasm-bindgen

> WebAssembly can only send and receive number between JavaScript and WebAssembly module.

In order to pass other data (like String, Objects, Functions), we should create a binding file. 

The binding file does the following:
* It converts string or object to something that WebAssembly module understands.
* It converts the returned value from WebAssembly module into string or object that JavaScript understands.

But converting them every time is a mundane task and error-prone. Fortunately Rust world came up with `wasm-bindgen`.

## [wasm-bindgen](https://rustwasm.github.io/docs/wasm-bindgen/)

> Facilitating high-level interactions between wasm modules and JavaScript  - wasm-bindgen

The wasm-bindgen provides a channel between JavaScript and WebAssembly to communicate something other than numbers, i.e., objects, strings, arrays, etc.,

### Write some code ✍️

Let us start with `hello_world` with wasm-bindgen.


Create a new project with cargo. 

```
$ cargo new --lib hello_world
Created library `hello_world` package
```

This creates a new Rust project with the necessary files.

Once created open the project in your favourite editor.

Open the `Cargo.toml` file and add the `wasm-bindgen` dependency.

---

Check out my book on Rust and WebAssembly [here](https://sendilkumarn.com/wasm-book)

---

```
[package]
name = "hello_world"
version = "0.1.0"
authors = ["Sendil Kumar <sendilkumarn@live.com>"]
edition = "2018"

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

[dependencies]
wasm-bindgen = "0.2.56"
```

Open the `src/lib.rs` file and replace the contents with the following:

```
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn hello_world() -> String {
 "Hello World".to_string()
}
```

We imported the `wasm_bindgen` library `use wasm_bindgen::prelude::*;`. 

We annotated the hello_world() function with `#[wasm_bindgen]` tag.

The `hello_world()` function returns a `String`.

To generate the WebAssembly module run:

```
$ cargo build --target=wasm32-unknown-unknown
```

The `cargo build` command does not generate any JavaScript binding file. In order to generate the binding files, we need to run the wasm-bindgen CLI tool on the generated WebAssembly module.

Install `wasm-bindgen CLI` to generate the binding file. 

Use `cargo` to install `wasm-bindgen-CLI`:

```
$ cargo install wasm-bindgen-cli
```

Once successfully installed run the wasm-bindgen CLI on the generated WebAssembly module.

```
$ wasm-bindgen target/wasm32-unknown-unknown/debug/hello_world.wasm --out-dir .
```

We instruct `wasm-bindgen` to generate the binding JavaScript for the generated WebAssembly module. 

The `--out-dir` flag instructs the `wasm-bindgen` where to generate files. The files are generated in the current folder.

This generates the following files:

```
$ ls -lrta
76330 hello_world_bg.wasm
 1218 hello_world.js
  109 hello_world.d.ts
  190 hello_world_bg.d.ts
```

The `wasm-bindgen CLI` takes the WebAssembly module (the output of the cargo build) as input and generate the bindings. The size of the binding JavaScript file is around `1.2 KB`. The `hello_world.js` does all the translations (that are required) between JavaScript and the WebAssembly modules.

The wasm-bindgen CLI along with the binding file generates the [type definition file](https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html) `hello_world.d.ts`.  

The type definition file for the WebAssembly Module (`hello_world.d.ts`).

The rewritten WebAssembly module `hello_world.wasm` that takes advantage of the binding file.

The JavaScript binding file is enough for us to load and run the WebAssembly Module. 

> If you are using [TypeScript](https://www.typescriptlang.org), then the type definition will be helpful.

## Inside the binding file

The binding file imports the WebAssembly module.

```
import * as wasm from './hello_world_bg.wasm';
```

Then we have the [TextDecoder](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder), to decode the String from the [ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer).

_Since there are no input arguments available there is no need for [TextEncoder](https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder) (that is to encode the String from JavaScript into the shared memory)._

The `wasm-bindgen` generates only the necessary functions inside the binding file. This makes the binding file as small as 1.2KB.

> Modern browsers have built-in `TextDecoder` and `TextEncoder` support. The `wasm-bindgen` checks and uses them if they are available else it loads it using `polyfill`.

```
const lTextDecoder = typeof TextDecoder === 'undefined' ? require('util').TextDecoder : TextDecoder;
let cachedTextDecoder = new lTextDecoder('utf-8');
```

The shared memory between JavaScript and the WebAssembly module need not be initialised every time. We initialise it once and use it across. 

We have the following two methods to load the memory once and use it.

```
let cachegetInt32Memory0 = null;
function getInt32Memory0() {
    if (cachegetInt32Memory0 === null || cachegetInt32Memory0.buffer !== wasm.memory.buffer) {
        cachegetInt32Memory0 = new Int32Array(wasm.memory.buffer);
    }
    return cachegetInt32Memory0;
}
```

```
let cachegetUint8Memory0 = null;
function getUint8Memory0() {
    if (cachegetUint8Memory0 === null || cachegetUint8Memory0.buffer !== wasm.memory.buffer) {
        cachegetUint8Memory0 = new Uint8Array(wasm.memory.buffer);
    }
    return cachegetUint8Memory0;
}
```

The Rust code returns `String` to the JavaScript land. The String is passed via the shared memory. 

The Shared memory is nothing but an ArrayBuffer. So we can need only the pointer to the offset (location where it is stored) and the length of the String to retrieve the String. Both the index of the location and the length are just numbers. They are passed from the WebAssembly land to JavaScript without any problem.

The following function is used for retrieving the String from the WebAssembly module:

```
function getStringFromWasm0(ptr, len) {
    return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
}
```

* `ptr` is an index where the offset of the location.
* `len` is the length of the String.

Finally, we have the `hello_world` function.

```
/**
* @returns {string}
*/
export function hello_world() {
    try {
        wasm.hello_world(8);
        var r0 = getInt32Memory0()[8 / 4 + 0];
        var r1 = getInt32Memory0()[8 / 4 + 1];
        return getStringFromWasm0(r0, r1);
    } finally {
        wasm.__wbindgen_free(r0, r1);
    }
}
```

The `hello_world` function is exported. We get the pointer and length from the shared memory buffer. Then pass the two numbers (r0, r1) to the `getStringFromWasm` function. 

The `getStringFromWasm` function returns the String from the shared Array Buffer with `ptr` and `len`.


Once we received the output, we clear the allocated memory using `wasm.__wbindgen_free(r0, r1)`.

---

### cargo-expand

To understand what happens on the Rust side, let us use the `cargo-expand` command to expand the macro and see how the code is generated.

> Note: Check [here](https://github.com/dtolnay/cargo-expand) for how to install cargo expand. It is not mandatory for the course of this book. But they will help you understand what wasm-bindgen actually generates.

Open your terminal, go to the project's base directory and run `cargo expand --target=wasm32-unknown-unknown > expanded.rs`.

The above command generates `expanded.rs`. 

The simple `#[wasm_bindgen]` annotation changes / adds the verbose part of exposing the function. All the necessary metadata that is required for the compiler to convert to WebAssembly module. 

> Note: Check out more about the internals of the #[wasm_bindgen] command [here](https://hacks.mozilla.org/2018/04/javascript-to-rust-and-back-again-a-wasm-bindgen-tale/)

The `expanded.rs` has the `hello_world` function.

```
pub fn hello_world() -> String {
    "Hello World".to_string()
}
```

The `__wasm_bindgen_generated_hello_world` function is an auto generated.

```
#[allow(non_snake_case)]
#[export_name = "hello_world"]
#[allow(clippy::all)]
pub extern "C" fn __wasm_bindgen_generated_hello_world(
) -> <String as wasm_bindgen::convert::ReturnWasmAbi>::Abi {
    let _ret = { hello_world() };
    <String as wasm_bindgen::convert::ReturnWasmAbi>::return_abi(_ret)
}
```

The `#[export_name = "hello_world"]` exports the function with the name `hello_world`.

The function returns `<String as wasm_bindgen::convert::ReturnWasmAbi>::Abi`. We will see more about this type in the later posts. But if you want to understand what happens here read [this](https://hacks.mozilla.org/2018/04/javascript-to-rust-and-back-again-a-wasm-bindgen-tale/) post.

The function returns the String in the format the binding JavaScript file (`ptr` and `len`).

---

### Run it 🏃‍♂️

Instead of running them using local web server we can load and run the generated files we can use bundlers like [Webpack](https://webpack.js.org) or [Parcel](https://parceljs.org). 

We will see more in detail about how these bundlers help in the later chapters. 

For now let's see how to run and load the generated files:

> Note the following setup is common and we will refer it as the `"default"` webpack setup in the future examples.

Create a `webpack.config.js` to configure Webpack how to handle files.

```
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {     
    entry: './index.js',
    output: {         
        path: path.resolve(__dirname, 'dist'),         
        filename: 'bundle.js',     
    },
    plugins: [         
        new HtmlWebpackPlugin(),         
    ],
    mode: 'development'
};
```

This is a standard webpack configuration file with an `HTMLWebpackPlugin`. This plugin helps us to generate a default `index.html` rather than we create one.

Let us add a `package.json` file to bundle the dependencies for running the Webpack and scripts to run.

```
{
    "scripts": {
        "build": "webpack",
        "serve": "webpack-dev-server"
    },
    "devDependencies": {
        "html-webpack-plugin": "^3.2.0",
        "webpack": "^4.41.5",
        "webpack-cli": "^3.3.10",
        "webpack-dev-server": "^3.10.1"
    }
}
```

Create an `index.js` file to load the binding JavaScript that in turn loads the WebAssembly module generated.

```
import("./hello_world").then(module => {
    console.log(module.hello_world());
});
```

Now head over to the terminal and then install the npm dependencies using.

```
$ npm install
```

Run the `webpack-dev-server` using

```
$ npm run serve
```

Go to the URL on which webpack-dev-server serves (defaults to [http://localhost:8080](http://localhost:8080)) and open the developer console in the browser to see "Hello World" printed.

---

### wasm-bindgen options

Let us take a look at the various options `wasm-bindgen` supports.

`--out-dir` - generates the file in a particular directory. 

`--out-name` - set a custom filename.

wasm-bindgen has the following flags:

#### --debug

The `--debug` option includes extra debug information in the generated WebAssembly module. This will increase the size of the WebAssembly module. But it is useful in development.

#### --keep-debug

WebAssembly modules may or may not have [custom sections](https://webassembly.github.io/spec/core/appendix/custom.html) (We will them in the later blogs). This custom section can be used to hold the debugging information. They will be helpful while debugging the application (like in-browser dev tools). This increases the size of the WebAssembly module. This is useful in development.

#### --no-demangle

This flag tells the wasm-bindgen not to `demangle` the Rust symbol names. Demangle helps the end-user to use the same name that they have defined in the Rust file.

#### --remove-name-section

This will remove the debugging name section of the file. We will see more about various sections in the WebAssembly module later. This will decrease the size of the WebAssembly module.

#### --remove-producers-section

WebAssembly modules can have a [producer section](https://github.com/WebAssembly/tool-conventions/blob/master/ProducersSection.md). This section holds the information about how the file is produced or who produced the file.

By default, producer sections are added in the generated WebAssembly module. With this flag, we can remove it.
It saves a few more bytes.

The `wasm-bindgen` provide options to generate the binding file for both `Node.js` and the `browser` environment. Let us see those flags.

`--nodejs` - Generates output that only works for Node.js. No ESModules.
`--browser` - Generates output that only works for browser With ESModules.
`--no-modules`- Generates output that only works for browser. No ESModules. Suitable for browsers that don't support ESModules yet.

The type definition files (\*.d.ts) can be switched off by using `--no-typescript` flag.

---

If you have enjoyed the post, then you might like my book on Rust and WebAssembly. Check them out [here](https://sendilkumarn.com/wasm-book)

---

---

👇 Repo 👇

{% github sendilkumarn/rustwasm-introduction no-readme %}

---

### Interested to explore more...

To know more about the custom section. Check out [here](https://webassembly.github.io/spec/core/appendix/custom.html)

Check out more about webpack [here](https://webpack.js.org/)

Check out [this](https://hacks.mozilla.org/2018/03/es-modules-a-cartoon-deep-dive/) awesome blog post to know more about the ECMAScript modules.

---

You can follow me on [Twitter](https://twitter.com/sendilkumarn).

If you like this article, please leave a like or a comment. ❤️

---