wasm-bindgen making Rust and JavaScript interoperability easy

Reading Time: 4 minutes

If you are familiar with WebAssembly and know how to run the wasm modules, then you would know how hard it was to make a Rust wasm module run using JavaScript. So to solve that, there is a tool called wasm-bindgen. It is a wonderful tool that allows greater interoperability between Rust WebAssembly modules and JavaScript. So, let us begin and see how to use it.

Introduction

wasm-bindgen is a tool that allows Rust to see JavaScript classes, expose and invoke callbacks in either language, send strings as function parameters, and return complex values, all while maintaining Rust’s strict sharing rules. This goes the same for JavaScript. It allows JavaScript to use Rust functions and structures and invoke callbacks as well. This makes the two languages work so smoothly with each other that it does not seem that they are different.

At the basic level, wasm-bindgen injects some metadata into your compiled WebAssembly module. Then, a separate command-line tool reads that metadata to generate an appropriate JavaScript wrapper containing the kinds of functions, classes, and other primitives that the developer wants to be bound to Rust.

Installing wasm-bindgen

wasm-bindgen uses procedural macros and a few other features. Installing wasm-bindgen is quite easy. Run the following command in the terminal.

$ cargo install wasm-bindgen-cli

If you have previously installed wasm-bindgen and want to install the latest version then use –force at the end of the above command.

You would also need npm to run the server, Click here to download npm.

Create a new Rust WebAssembly Project

To get started with wasm-bindgen, first create a new Rust Project. Run the following command to create a new rust library project.

$ cargo new bindgen_demo --lib

Now, edit the Cargo.toml file. Change the library type to cdylib and add the wasm-bindgen dependency. Also, make sure to delete the “2018 edition” line. Now your Cargo.toml will look like this.

[package]
name = "bindgen_demo"
version = "0.1.0"
authors = ["Your Name <your@address.com>"]

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

[dependencies]
wasm-bindgen = "0.2"

Now, open the lib.rs file and replace its content with the following code.

extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
extern "C" {
    fn alert(s: &str);
}

// Export a 'hello' function
#[wasm_bindgen]
pub fn hello(name: &str) {
    alert(&format!("Hello, {}!", name));
}

#[wasm_bindgen] triggers the invocation of Rust compile-time macro that generates some code on your behalf. Some of that code is used in your .wasm file and some of it will be used to generate the corresponding javascript using the wasm-bindgen CLI.

The first binds the alert() function in Rust program to the alert() JavaScript function. With this, the Rust code that invokes the alert() function will be converted into a code that calls the JavaScript alert() function inside a WebAssembly module.

The second binding exposes the hello() function to be used by the javascript. However, in this case, it takes a reference to a string as a parameter. Since WebAssembly does not support the string type, so a boilerplate code will be inserted to use the complex data structures.

Building the project

Now, it’s time to build the project. Run the following command to build the project.

$ cargo build --target wasm32-unknown-unknown

Now use the following command to produce a new wasm and javascript wrapper files.

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

Now, if you will see the auto-generated bindgen_demo.js file then you will see the hello() function we created in the rust file.

export function hello(arg0) {
    const ptr0 = passStringToWasm(arg0);
    const len0 = WASM_VECTOR_LEN;
    try {
        return wasm.hello(ptr0, len0);
    } finally {
        wasm.__wbindgen_free(ptr0, len0 * 1);
    }
}

This hello() function will be used by our index.js file which we will now create. Create an index.js file and copy the following code.

const wasm = import('./bindgen_demo');
wasm
    .then(h => h.hello("world!"))
    .catch(console.error);

Now, this looks like something pure javascript without any of the long codes that we wrote previously to insert a wasm module in javascript. This invokes the hello function in bindgen_demo.js file auto-generated by wasm-bindgen.

Executing the project

Now add a webpack.config.js file to manage your javascript bundles. Also, make sure that the entry point is index.js file.

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

module.exports = {
    entry: './index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'index.js',
    },

    plugins: [
        new HtmlWebpackPlugin(),
        new webpack.ProvidePlugin({
            TextDecoder: ['text-encoding', 'TextDecoder'],
            TextEncoder: ['text-encoding', 'TextEncoder']
        })
    ],

    mode: 'development'
};    

Also, add a package.json file.

{
    "scripts": {
    "build": "webpack",
    "serve": "webpack-dev-server"
    },

    "devDependencies": {
        "text-encoding": "^0.7.0",
        "html-webpack-plugin": "^3.2.0",
        "webpack": "^4.11.1",
        "webpack-cli": "^3.1.1",
        "webpack-dev-server": "^3.1.0"
    }
}

Now everything is set. Run the npm run serve command to run the server.

If you see this error while executing npm run server command then run npm install to fix this error and then again run npm run server.

Awesome! We have now used a Rust function inside a javascript and a javascript function inside a Rust module using wasm-bindgen.

If you want to read more content like this?  Subscribe to Rust Times Newsletter and receive insights and latest updates, bi-weekly, straight into your inbox. Subscribe to Rust Times Newsletter: https://bit.ly/2Vdlld7.


Knoldus-blog-footer-image