WebAssembly (abbreviated WASM) is a binary instruction format for a stack-based virtual machines. WASM is designed as a portable compilation target for programming languages, enabling deployment on the web for client and server applications.

Introducing WebAssembly using Rust

Why use WebAssembly (WASM)?

WASM allows developers to build high-speed web apps in the language of their choice. However it would be a wrong notation that WebAssembly is only for web applications. It can be used for other non-web based applications as well

WebAssembly is not going to replace JavaScript or any other language for that matter. It is just a binary format that promises near native performance on any target. It can be compilation target for any programming language.


In this article we will cover how to compile Rust code to WebAssembly. We can write some Rust code, call JavaScript functions from it or expose functions to JavaScript for further use.

Use cases for WebAssembly

There are two major use cases for Rust and WebAssembly:

  • Entire Application: Build an entire web application using Rust and compile it to WebAssembly
  • Partial application: Build partial application using Rust and compile it to WebAssembly. Then use it with an existing JavaScript frontend.

In this article we are going to cover latter use case for illustration. If you are interested to build complete web application using rust, check out yew project.

Introducing WebAssembly using Rust - Conference


To build a wasm app we will be using package wasm-pack to build a package. wasm-pack is a tool for building JavaScript packages in Rust. This implementation will contain WebAssembly and JavaScript code, so to run that package users will not need Rust installed. Since final product would be WebAssembly and JavaScript, users will not even know its built using Rust.

Environment Setup

To build package we would need and environment setup. Let’s go through what all softwares and dependencies needed.

Rust

First we would definitely need Rust installed. If you do not have it setup already, please head to Rust Installation page and install by following instructions. This installs a tool called rustup, which lets you manage multiple versions of Rust. By default, it installs the latest stable Rust release, which you can use for general Rust development.

rustup installs rustc, the Rust compiler. It also installs cargo, package manager for Rust, rust-std, standard libraries of Rust’s, and documentations rust-docs.

Note: Pay attention to the post-install note about needing cargo’s bin directory in your system PATH. This is added automatically, but you must restart your terminal for it to take effect.

wasm-pack

Now to trans-compile our code into WebAssembly; we need an additional tool, a wasm-pack package. Once installed and added as dependency to our project, this will help compile the code to WebAssembly, as well as produce code structure to be used in browser.

To download and install it, enter the following command into your terminal:

cargo install wasm-pack

Introducing WebAssembly using Rust - Building our WebAssembly package

Building our WebAssembly package

Now let us create a package in Rust. Open a command prompt, navigate to a folder where you would like to create this package and then run following command in command prompt

cargo new --lib first-wasm
//=> Created library `first-wasm` project

If you list content of present working directory, you should notice a folder named first-wasm. This is where a rust package has been created. Now go into first-wasm folder and list its contents. If you are on unix based OS, you may use tree command, like following

tree
//+-- Cargo.toml
//+-- src
//    +-- lib.rs

Let’s dissect what we are looking at. Cargo.toml is package file, contains or relay important information about package (i.e config, structure, dependencies and how to build). If you are already familiar with Gemfile in Bundler in Ruby or package.json in NodeJS’s npm. It is similar to that.

Next we are going to look at src/lib.rs that cargo has generated for us.

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        assert_eq!(2 + 2, 4);
    }
}

It is just an assertion test or a placeholder. Hence we are not going to use this. You can go ahead and remove this code.

Let’s write our WASM code in Rust

Let us write some Rust code that would transpile to WASM. Put following content into src/lib.rs. Make sure that you removed existing code.

use wasm_bindgen::prelude::*;

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

#[wasm_bindgen]
pub fn greet(name: &str) {
    alert(&format!("Hello, {}!", name));
}

Let’s dissect the code above. It has three major parts that we will discuss here. We will not be going into Rust dictionary and syntax, for that please read Rust By Example Book

Using wasm-bindgen to communicate between Rust and JavaScript

The first line in our Rust code is following:

use wasm_bindgen::prelude::*;

Here the use keyword, imports code from library into your code. In Rust, libraries are called crates. In this case, use is importing everything from the wasm_bindgen::prelude module. We use these features in the next section. wasm-bindgen is coming from a wasm-pack package to create a bridge between Rust code and JavaScript. It allows JavaScript to call Rust API and/or Rust function with required parameters. In short, it generates bindings between JavaScript and Rust

We use wasm-bindgen’s functionality in our package. In fact, that’s the next section.

Calling external JavaScript functions from Rust

In our code, the next four lines looks like following:

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

The call inside the #[] is called an attribute, and it modifies the next statement. In our case the statement is question is extern. This tells Rust that we want to call some externally defined functions. The attribute points to where to find those functions, in our case it is wasm-bindgen and it handles the binding.

The next line defines a public function in Rust, named alert, which takes a string as argument and assign it to s

As you already may know, alert function is available in JavaScript and we are calling it from Rust using wasm-bindgen

Whenever you want to call any JavaScript functions, you just add them to this file, and wasm-bindgen will take care of setting everything up for you.

Beware that, not everything is supported yet, but we’re working on it. At the time of writing this article this functionality is still Work-in-Progress.

Producing Rust functions that JavaScript can call

Now look at remaining lines of code, below:

#[wasm_bindgen]
pub fn greet(name: &str) {
    alert(&format!("Hello, {}!", name));
}

Once again, we see the #[wasm_bindgen] attribute. In this case, it’s not modifying an extern block, but a public function fn; this means that we want this Rust function to be able to be called by JavaScript.

This is opposite of extern; these are the functions that we are exposing to JavaScript front-end from Rust via WebAssembly.

This function is named greet, and takes one argument name, a string. It then calls the alert function we asked for in the extern block above. It passes a call to the format! macro, which lets us interpolate strings.

The format! macro takes two arguments in this case, a format string, and a variable to put in it. The format string is the "Hello, {}!" bit. It contains {}, where variables will be interpolated. The variable we’re passing is name, the argument to the function, so if we call greet("Pankaj") we should see "Hello, Pankaj!".

This is passed to alert(), so when we call this function we will see an alert box with "Hello, Pankaj!" in it. Now since our library is written, let’s build it.

Compiling our code to WebAssembly

Sigh!, Now we came to last part. We get to compile our code to WebAssembly and then get to use in our front-end code. Let’s open Cargo.toml and change its content with below code:

[package]
name = "first-wasm"
version = "0.1"
authors = ["Your Name <you@example.com>"]
description = "A sample project with wasm-pack"
license = "MIT/Apache-2.0"
repository = "https://github.com/yourgithubusername/first-wasm"
edition = "2023"

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

[dependencies]
wasm-bindgen = "v0.2"

Fill in values with values specific to you (i.e github repo url). The big part to add is the [package] config. The [lib] part tells Rust to build a cdylib version of our package. For more, consult the Cargo and Rust Linkage documentation.

The last section is the [dependencies] section. Here’s where we tell Cargo what version of wasm-bindgen we want to depend on; in this case, that’s any 0.2.z version (but not 0.3.0 or above).

Building the package

Now that we’ve got everything set up, let’s build the package. Run following command to build package

wasm-pack build --target web

Command above tells wasm-pack to build current folder targeting web (or browser) use. --target is specified as web. We can also build it for nodejs. On first run, it may take a lot of time; as it needs to do number of things. To learn about them in detail, check out this blog post on Mozilla Hacks. In short, wasm-pack build:

  • Compiles Rust code to WebAssembly.
  • Runs wasm-bindgen on that WebAssembly, generating a JavaScript file that wraps up that WebAssembly file into a module the browser can understand.
  • Creates a pkg directory and moves that JavaScript file and your WebAssembly code into it.
  • Reads your Cargo.toml and produces an equivalent package.json.
  • Copies your README.md (if you have one) into the package.

After the build completes, you must have your package under folder named pkg

A digression about code size

If we look at the size of code generated, it can go in hundreds of kilobytes. This is because we have not optimized it yet. Since this is not in the scope of this article, we are going to skip it for now. But if you are curious, you shall have a look at wasm-shrink crate. Further reading is available on Rust WebAssembly Working Group’s documentation on optimizing .wasm further

Using the package on the web

Now, since we have our hands on final produce of this .wasm package. Let’s run that in browser

Create a folder, copy over pkg folder as generated by wasm-pack under folder you just created. Create an index file named index.html; with following contents:

<!DOCTYPE html>
<html lang="en-US">
  <head>
    <meta charset="utf-8" />
    <title>first-wasm example</title>
  </head>
  <body>
    <script type="module">
      import init, { greet } from "./pkg/first_wasm.js";
      init().then(() => {
        greet("WebAssembly");
      });
    </script>
  </body>
</html>

Now, open this index.html file in browser. There are multiple ways to do it. Like use [http-server] npm package or use python static server for a folder, you can run following command

python -c "import SimpleHTTPServer;SimpleHTTPServer.test()"

The script in this file will import the js glue code, initialize the wasm module, and call the greet function we wrote in rust.

Note: Make sure to use an up-to-date web server that supports the application/wasm MIME type. Older web servers might not support it yet.

Load index.html from the web server (if you used the Python example, browse at http://localhost:8000). An alert box appears on the screen, with Hello, WebAssembly! in it. We’ve successfully called from JavaScript into Rust, and from Rust into JavaScript. Yay!

Making our package available to npm

NPM, is very popular package management used to manage dependencies for Node.JS. Presently npm is most preferred way to share your libraries to world out there, be it front-end or backend. If you wish to use the WebAssembly module, we created; with npm, we’ll need to make a few changes.

We will start by recompiling our Rust package with target bundler option:

wasm-pack build --target bundler

Introducing WebAssembly using Rust - WebAssembly with npm

Install Node.js and npm

Since we have built our WebAssembly package for npm. We need to have Node.JS and NPM installed (if not already installed)

To get Node.JS and NPM, head to Node.JS Download Page and follow instruction. Once done, check your installation with node command. Fire up a terminal window and run following commands

node -v
npm -v

Next, we are going to link our package to npm. Once linked it will be available for installation in local system. To do this, we shall run following command in terminal.

cd ./pkg
npm link

We now have an npm package, that is written in Rust, trans-compiled to WebAssembly. Now this package is available to users via npm. Once part of project, it can be called from within our application. User need not have Rust installed for this to work on their systems. The package code included is WebAssembly code not the Rust source.

Using the npm package on the web

Now let’s use our npm package in a website. There are many bundling tools out there to manage packages for front-end. We would be using webpack to show how to use our npm package.

Fire up a new terminal and make a directory using following command:

mkdir -p myProject
cd myProject
npm link first-wasm

Now to convert this folder into Node.JS project, run npm init and give your input on prompt. Once done you should see a package.json file. Now run following commands in terminal:

npm install first-wasm --save
npm install webpack --save-dev
npm install webpack-cli --save-dev
npm install webpack-dev-server --save-dev

Now if you open package.json file in your favorite editor, you should see something like following

"dependencies": {
  "first-wasm": "^0.1.0"
},
"devDependencies": {
  "webpack": "^4.25.1",
  "webpack-cli": "^3.1.2",
  "webpack-dev-server": "^3.1.10"
}

Now please add webpack-dev-server to your serve script. This will help us bringing up a server to serve our files.

"scripts": {
  "serve": "webpack-dev-server"
  ....snipped
}

Huff, Now we need to configure webpack. To do that we shall create webpack.config.js and put in following code in it:

const path = require("path");
module.exports = {
  entry: "./index.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "index.js",
  },
  mode: "development",
};

Since our webpack config is telling webpack to enter from index.js. Let’s create an index.js file and toss in following content in it:

import("./node_modules/first-wasm/first_wasm.js").then( ( script ) => {
  script.greet("WebAssembly with npm");
});

This is not the best practices advised to import and use packages, however since this is just to showcase our WebAssembly package, it should be okay.

Once it’s loaded, it calls the greet function from that module, passing “WebAssembly” as a string.

Note how there’s nothing special here, yet we’re calling into Rust code. As far as the JavaScript code can tell, this is just a normal module.

Finally, we need to modify the HTML file; open the index.html file and replace the current contents with the following:

<!DOCTYPE html>
<html lang="en-US">
  <head>
    <meta charset="utf-8" />
    <title>first-wasm example</title>
  </head>
  <body>
    <script src="./index.js"></script>
  </body>
</html>

Phew! we are almost done. Let’s run npm install and then fire up our file server by running following commands:

npm install
npm run serve

Command above shall start a web server at http://localhost:8080. Open that in web browser and an alert box shall appear on the screen; with Hello, WebAssembly with npm!. We’ve successfully built WebAssembly with Rust, trans-compiled it to WebAssembly and converted it to npm package and then used it in a Node.JS project. Yay!

Conclusion

We finally reached end of this article. We hope you have found it useful so far and learnt something new today. There’s lots of exciting work going on in WebAssembly space. So stay tuned. See you later

Further Reading


About The Author

I am Pankaj Baagwan, a System Design Architect. A Computer Scientist by heart, process enthusiast, and open source author/contributor/writer. Advocates Karma. Love working with cutting edge, fascinating, open source technologies.

  • To consult Pankaj Bagwan on System Design, Cyber Security and Application Development, SEO and SMO, please reach out at me[at]bagwanpankaj[dot]com

  • For promotion/advertisement of your services and products on this blog, please reach out at me[at]bagwanpankaj[dot]com

Stay tuned <3. Signing off for RAAM