Rust on the front-end
Tags: rust, beginners, webassembly, webdev
Published: 2021-07-04 15:51:17

Up until now, JavaScript has been the only ubiquitous language available in browsers. It has made JavaScript much more popular than its design (and its associated flaws) would have allowed. Consequently:

* The number of JavaScript developers has grown tremendously and steadily
* The ecosystem around front-end JavaScript has become more extensive and much more complex
* The pace of changes has increased so that developers complain about JavaScript fatigue
* Interestingly enough, JavaScript sneaked on the back-end via Node.js
* etc.

I don't want to start a holy war about the merits of JavaScript, but IMHO, it only survived this far because of its role in browsers. In particular, current architectures move the responsibility of executing the code from the server to the client. It puts a lot of strain on the latter. There are not many ways to improve performance: either buy more powerful (and expensive!) client machines or make the JavaScript engines better.

Comes [WebAssembly](https://webassembly.org/).

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

Wasm is not designed to replace JavaScript in the browser (yet?) entirely but to improve the overall performance.
Though Rust is intended for system programming, it does offer compilation to WebAssembly.

## Rust and WebAssembly

Learning Rust is a long process; learning Rust and WebAssembly even more so. Fortunately, people of goodwill already made the journey a bit easier. They wrote an entirely free and online [tutorial book](https://rustwasm.github.io/docs/book/) dedicated solely to this subject, available under a friendly Open Source license.

As both a learner and a trainer, I know how hard it is to create a good tutorial:

1. Either you provide a step-by-step progression path, with solutions along the way, and it becomes just a matter of copy-pasting
2. Or you provide something less detailed, but you run the risk that some learners get blocked and cannot finish.

The book avoids both pitfalls as it follows the first pattern but provides optional problems at the end of each chapter. To avoid blocking the learner, each problem provides a general hint to lead learners to the solution. If you cannot (or do not want to) solve a specific problem, you can continue onto the next chapter anyway. Note that in the [associated Git repository](https://github.com/ajavageek/rust-game-of-life), each commit references either a standard copy-paste step or a problem to solve.

Moreover, the book provides two sections, the proper tutorial, and a reference part. Thus, you can check for the relevant documentation bits during the tutorial and deepen your understanding after it.

## A Rust project

The first tutorial step focuses on the [setup](https://rustwasm.github.io/docs/book/game-of-life/setup.html). It's short and is the most "copy-pastey" of all. The reason for that is that it leverages [cargo-generate](https://github.com/cargo-generate/cargo-generate), a Cargo plugin that allows creating a new project by using an existing Git repository as a template. In our case, the template is a [Rust project](https://github.com/rustwasm/wasm-pack-template) ready to compile to Wasm. The project's structure is:

```
wasm-game-of-life/
├── Cargo.toml
└── src
    ├── lib.rs
    └── utils.rs
```

It's the structure of "standard" Rust projects. Now is an excellent time to look at the `Cargo.toml` file. It plays the `pom.xml` role, listing meta-information about the package, dependencies, compilation hints, etc.

```toml
[package]                                              // 1
name = "wasm-game-of-life"
version = "0.1.0"
authors = ["Nicolas Frankel <nicolas@frankel.ch>"]
edition = "2018"

[lib]                                                  // 2
crate-type = ["cdylib", "rlib"]                        // 3

[features]
default = ["console_error_panic_hook"]

[dependencies]
wasm-bindgen = "0.2.63"                                // 4

# Rest of the file omitted for clarity purposes
```

1. Meta-information about the package
2. Produce a library, not a binary
3. Produce both a Rust library as well as a dynamic system library
4. The Wasm-producing dependency

## Integrating the front-end

The project as it stands is not very interesting: you cannot see the magic happening. The next step in the tutorial is to add a web interface to interact with the Rust code compiled to Wasm.

As for the previous step, a command allows copying code from GitHub. Here, the command is `npm`, and the template is [create-wasm-app](https://github.com/rustwasm/create-wasm-app). Let's run the command:

```bash
npm init wasm-app www
```

The previous command outputs the following structure:

```
wasm-game-of-life/
└── www/
    ├── package.json                   // 1
    ├── webpack.config.js              // 2
    ├── index.js                       // 3
    ├── bootstrap.js                   // 4
    └── index.html
```

1. Mirror image of `Cargo.toml` for NPM projects, configured for Wasm
2. Webpack configuration
3. Main entry-point into the "application": call the Wasm code
4. Asynchronous loader wrapper for `index.js`

At this point, it's possible to execute the whole code chain, provided we go through the required build steps:

1. Compile Rust code to Wasm
2. Generate the JavaScript adapter code. You can run this step and the previous one with a single call to `wasm-pack`. Check the generated files in the `pkg` folder.
3. Get the NPM dependencies with `npm install`
4. Run a local webserver with `npm run start`.

Browsing to <http://localhost:8080> should display a simple `alert()` message.

At the end of this section, the exercise mandates you to change the `alert()` to `prompt()` to provide parameterization. You should change the Rust code accordingly and re-compile it. The web server should reload the new code on the fly so that refreshing the page should display the updated code.

My idea behind this post is not to redo the whole tutorial but to focus on the juicy parts. With Rust on the front-end, it boils down to:

1. Call Rust from JavaScript
2. Call JavaScript from Rust
3. Call browser APIs from Rust

## Call Rust from JavaScript

To call Rust from JavaScript, you need to compile the Rust code to Wasm and provide the thin JavaScript wrapper. The template project already has it configured. You only need to use the `wasm-bindgen` macro on the Rust functions you want to make available.

```rust
#[wasm_bindgen]           // 1
pub fn foo() {
    // do something
}
```

1. Magic macro!

On the JavaScript side:

```javascript
import * as wasm from "hello-wasm-pack";       // 1

wasm.foo();                                    // 2
```

1. Import everything from the `hello-wasm-pack` package into the `wasm` namespace
2. You can now call `foo()`

## Call JavaScript from Rust

The guiding principle behind the tutorial is [Conway's Game of Life](https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life). One way to initialize the board is to set each cell to either dead or alive randomly. Because the randomization should occur at runtime, we need to use JavaScript's `Math.random()`. Hence, we also need to call JavaScript functions from Rust.

Basic set up uses Foreign Function Interface via the `extern` keyword:

```rust
#[wasm_bindgen]
extern "C" {                                 // 1
    #[wasm_bindgen(js_namespace = Math)]     // 2
    fn random() -> f64;
}

#[wasm_bindgen]
fn random_boolean() -> bool {
    random() < 0.5                           // 3
}
```

1. It's not C code, but this is the correct syntax anyway
2. Generate the Rust interface so it can compile
3. Use it

While this works, it's highly error-prone. Alternatively, the `js-sys` crate provides all available bindings out-of-the-box:

>Bindings to JavaScript’s standard, built-in objects, including their methods and properties.
>
>This does not include any Web, Node, or any other JS environment APIs. Only the things that are guaranteed to exist in the global scope by the ECMAScript standard.
>
>https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects
>
>-- [Crate js_sys](https://rustwasm.github.io/wasm-bindgen/api/js_sys/)

To set up the crate, you only need to add it to the relevant section in the manifest:

```toml
[dependencies]
js-sys = { version = "0.3.50", optional = true }  # 1

[features]
default = ["js-sys"]                              # 2
```

1. Add the dependency as optional
2. Activate the optional feature

I must admit that I don't understand why to set the dependency `optional` and activate it on another line. I'll leave it at that for now.

The previous configuration allows the following code:

```rust
use js_sys::Math;               // 1

#[wasm_bindgen]
fn random_boolean() -> bool {
    Math::random() < 0.5        // 2
}
```

1. Use `Math` from the `js_sys` crate
2. Compile fine with the Rust compiler and call JavaScript's `Math.random()` at runtime

## Call browser APIs from Rust

The `js-sys` crate allows us to call JavaScript APIs inside of Rust code. However, to call client-side APIs, for example, `console.log()`, the `web_sys` crate is necessary.

>Raw API bindings for Web APIs
>
>This is a procedurally generated crate from browser WebIDL which provides a binding to all APIs that browsers provide on the web.
>
>This crate by default contains very little when compiled as almost all of its exposed APIs are gated by Cargo features. The exhaustive list of features can be found in crates/web-sys/Cargo.toml, but the rule of thumb for web-sys is that each type has its own cargo feature (named after the type). Using an API requires enabling the features for all types used in the API, and APIs should mention in the documentation what features they require.
>
>-- [Crate web_sys](https://rustwasm.github.io/wasm-bindgen/api/web_sys/)

Here's how to configure it:

```toml
[dependencies]
web-sys = { version = "0.3", features = ["console"] }
```

We can use the crate like this:

```rust
extern crate web_sys;                                 // 1

use web_sys::console;                                 // 2

#[wasm_bindgen]
impl Foo {
    pub fn new() -> Foo {
        utils::set_panic_hook();
        Universe {}
    }

    pub fn log(&self) {
        console::log_1("Hello from console".into());  // 3
    }
}
```

1. Require the `web-sys` create. I'm not sure if (or why) `extern` is needed.
2. Use the `console` package
3. `console::log_1()` translates into `console.log()` with one parameter at runtime

## Conclusion

In this post, we have detailed the three main points of using Rust in the browser: calling Rust from JavaScript, calling JavaScript from Rust, and calling browser APIs from Rust.

The following video gives you a taste of the final result; I can only encourage you to try the tutorial out by yourself.

{% youtube yabE04SD5bQ %}

The complete source code for this post can be found on Github:

{% github ajavageek/rust-game-of-life %}

**To go further:**

* [Rust and WebAssembly book](https://rustwasm.github.io/docs/book/)
* [Manifest format reference](https://doc.rust-lang.org/cargo/reference/manifest.html)
* [Linkage](https://doc.rust-lang.org/reference/linkage.html)
* [The wasm-bindgen Guide](https://rustwasm.github.io/docs/wasm-bindgen/)

_Originally published at [A Java Geek](https://blog.frankel.ch/start-rust/5/) on July 4<sup>th</sup>, 2021_