Getting started with WebAssembly and Rust

Rust 🦀 and WebAssembly 🕸

This small describes how to use Rust and WebAssembly together.

WebAssembly (or wasm) is a portable binary code format designed to run on an isolated virtual stack machine. Not a JavaScript replacement, but a useful tool

We live in a world where JavaScript code is run literally everywhere. We have it in the browser, as well as in the backend with Node.js, in desktop apps (Electron) and even on MCUs (low.js anyone?).

JavaScript has somewhat delivered the long standing promise (no pun intended) of “write once, run anywhere” that many have tried (and failed) to accomplish in the past. Unfortunately this is far from ideal, mostly because JavaScript was not designed with this goal in mind.

Several revisions of the ECMAScript standard were released to improve the language and address its limitations. Furthermore new and more expressive languages (yes TypeScript, we hear you) were released to add missing features while maintaining the complete compatibility with existing runtime engines. All this has led to the fragmented ecosystem we are all fighting everyday, making the development of large projects very challenging.

On top of that, JavaScript is slow. Even on heavily optimized runtimes JavaScript code is always slower than native code (yes V8, I’m talking about you and your speculative optimization). If this is surprising for you, please follow these steps:

  1. Leave the cave you are currently living in;
  2. Download the ECMAScript specification and count how many steps are required to perform just an OrdinaryGetOwnProperty;

Why Rust and WebAssembly?

Low-Level Control with High-Level Ergonomics

JavaScript Web applications struggle to attain and retain reliable performance. JavaScript’s dynamic type system and garbage collection pauses don’t help. Seemingly small code changes can result in drastic performance regressions if you accidentally wander off the JIT’s happy path.

Rust gives programmers low-level control and reliable performance. It is free from the non-deterministic garbage collection pauses that plague JavaScript. Programmers have control over indirection, monomorphization, and memory layout.

Small .wasm Sizes

Code size is incredibly important since the .wasm must be downloaded over the network. Rust lacks a runtime, enabling small .wasm sizes because there is no extra bloat included like a garbage collector. You only pay (in code size) for the functions you actually use.

Do Not Rewrite Everything

Existing code bases don’t need to be thrown away. You can start by porting your most performance-sensitive JavaScript functions to Rust to gain immediate benefits. And you can even stop there if you want to.

Plays Well With Others

Rust and WebAssembly integrates with existing JavaScript tooling. It supports ECMAScript modules and you can continue using the tooling you already love, like npm, Webpack, and Greenkeeper.

The Amenities You Expect

Rust has the modern amenities that developers have come to expect, such as:

  • strong package management with cargo,

  • expressive (and zero-cost) abstractions,

  • and a welcoming community! 😊

Getting started: Setup

To begin working with Rust and WebAssembly, one must obviously have the Rust toolchain installed. Nowadays the best (and recommended) way to manage the Rust toolchain is via the rustup tool.

Installing rustup is as easy as launching a Bash script (the rustup-init.sh script to be precise) with this one-line command:

# curl https://sh.rustup.rs -sSf | sh

If you are running Windows, luckily we leave in Nadella’s era and thus we have plenty of options to get Bash up and running: the Windows Subsystem for Linux, Git for Windows own Bash, Cygwin, just to name a few. If you don’t want to use Bash at all, there is a .exe version of rustup-init as well as standalone installers available for download from Rust language project website.

The script will download and compile all the required tools including rustc (the Rust compiler) and cargo (Rust’s package manager).

By default, the toolchain will be installed in $HOME/.cargo so be sure to add the $HOME/.cargo/bin to your PATH environment variable, or simply run the command:

# source $HOME/.cargo/env

when opening a new shell.

Once the Rust toolchain is installed, we can proceed installing wasm-pack. Wasm-pack is an all in one tool to build, test and publish Rust-generated wasm modules. To install it, simply type this command in your Bash prompt:

# curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh

or again, download the .exe file if you are on Windows from the official website.
Another useful tool to install is **cargo-generate**, which allows to create new projects based on existing templates. Since the Rust toolchain is already installed, we can use cargo itself to download, compile and install cargo-generate, with this command:

# cargo install cargo-generate

In case you do not have those installed, we recommend to install git, node and npm as these may come in handy pretty soon.

Our first WebAssembly module

Let’s now dive in and build our first Hello World project in Rust/WebAssembly: a web page that shows an alert!

First of all, we will create a new project using cargo generate, using a git repository as a template:

# cargo generate --git https://github.com/rustwasm/wasm-pack-template

When prompted for the project’s name, just type the name “hello”. The script will create the directory “hello” and populate it with the files shipped with the template:

  • Cargo.toml – which specifies dependencies and metadata for cargo (a sort of Rust’s equivalent of package.json);
  • src/lib.rs – which contains the code of our wasm module;
  • src/utils.rs – which provides common utilities to debug wasm code;

Let’s have a look at the lib.rs code in detail:

We obviously import the utils module, and then proceed using the wasm_bindgen, which is used to provide interoperation between our Rust and JavaScript code.

In this simple example, wasm_bindgen is used to import the browser’s alert() function, so our Rust code can call it and display the “Hello, hello!” message. That’s because the string created by cargo generate is “Hello, <project_name>!” and our project is named “hello”. Feel free to change this message according to your preference.

At the same time we also export the greet() function, so that the JavaScript code using our wasm module can call it.

When targeting wasm, the wee_alloc allocator is the preferred choice, as it was explicitly designed for smaller code size, although it can be a bit slower than other allocators.

Now that our code is ready, we can proceed building the project. To do that we will use the wasm-pack utility, which performs some sanity checks (ensuring we have the correct Rust version), compiles our code to wasm, download and compile all the dependencies and finally invoke wasm-bindgen to generate the JavaScript API bindings. All that with just one command:

# wasm-pack build

Once finished, the compiled artifacts are available in the pkg/ directory. This directory should contain these files:

  • hello_bg.wasm – our WebAssembly binary, which exports the greet() function;
  • hello.js – the JavaScript glue that implements interoperation layer between JS and wasm;
  • hello.d.ts – the TypeScript declarations for the JavaScript glue;
  • package.json – the metadata about this generated WebAssembly package;

Calling WebAssembly from a web page

The Rust project offers an npm init template to generate a web application that will call our WebAssembly module. Simply type the command:

# npm init wasm-app www

This will generate the web application inside the www/ directory, including a package.json file, a configuration file for webpack, an empty index.html and an empty JavaScript entrypoint index.js.

To use our WebAssembly package here, we simply must add it as a dependency in the package.json file, by adding the following line under the “devDependencies” node:

"hello": "file:../pkg"

Then, install the dependencies with the well known command:

# npm install

Once the installation is finished, modify the index.js file to call our WebAssembly greet() function, with this code:

import * as wasm from "hello"; 
wasm.greet();

This code imports our WebAssembly module using the JavaScript glue code in hello.js and calls the greet() function.

Since we have Webpack and its local server installed, we can run the application with the command:

# npm run start

Now point your browser to http://localhost:8080/ to be greeted with the message from our Rust code.

Congratulations, you have created your first Rust/WebAssembly project!

Conclusions

There is a lot more to cover about Rust and WebAssembly, considering best practices, optimization and interoperation between Rust and JavaScript (look up js-sys and web-sys).

#webassembly #rust #javascript #web-development

Getting started with WebAssembly and Rust
3.85 GEEK