Rust and WebAssembly - Introduction

Rust

The Rust language is designed to guide you naturally towards reliable goal that is efficient in terms of speed and memory usage - Nicholas Matsakis and Aaron Turon

Rust is a systems-level programming language. Rust provides low-level memory management to represent data efficiently.

Rust ensures safety. This makes it easy to write applications and not to worry about dangling pointers.

Rust makes concurrency painless.

Being a modern language, Rust provides well defined (designed) APIs. This makes Rust a desirable language while designing high performant applications, Command Line Interface applications (CLI apps) and others.

One of Rust's standout features is its compiler. The compiler guides you when making mistakes and empowers you to write better code.

The ownership model in Rust guarantees thread and memory safety.

Overall Rust ensures safety, concurrency, and makes you stay away from risks, crashes, and vulnerabilities.

World of Rust Web

Rust slowly but steadily progressing into the world of web development.

Rust is easy to get started (if you wrap your head around the ownership model) and backed by an awesome community that is ready to help.

Rust provides the first class support for the WebAssembly. Rust and WebAssembly toolchain makes it easier and faster to get started with WebAssembly.

JavaScript is easy. They enabled millions (even billions) of developers to start writing applications. The fast feedback loop while development and simple API. JavaScript provides a good performance. When optimised correctly, JavaScript may yield a better performance.

The performance that JavaScript provides is not reliable and consistent. Any optimisations to increase performance is not consistent across various JavaScript engines. This makes it difficult for developers to give a better optimised and consistent performance with JavaScript.

WebAssembly

WebAssembly promises better performance because it is statically typed, compiled, optimised for production, uses linear memory layout, and others.

WebAssembly makes it easy to run native code in the browser. Rust makes it easy to convert Rust to WebAssembly binary code.

Rust has the best tooling support that integrates well with tools that are familiar to JavaScript developers.


Setting up Rust toolchain for WebAssembly

Emscripten simplifies setting up the environment for compiling the C or C++ into the WebAssembly modules.

Rust considers WebAssembly as the first-class citizen. It provides a much better toolchain and support for WebAssembly.

Rust built its own WebAssembly toolchain inside the standard rust compiler (rustc). This enabled Rust developers to compile the Rust code into WebAssembly Modules.

Rustup is the Rust installer and version management tool. The Rustup tool is similar to nvm for Node.js. Rustup helps to install, update, and remove the rustc, cargo, and rustup itself.

Rustc is the Rust Compiler. It converts your source code into the library or executable.

Cargo is the package manager for Rust. Cargo is similar to Node's NPM. Cargo downloads the package dependencies, compiles, packs, and uploads them to the Rust crates library (crates.io).

Cargo downloads your Rust project's dependencies and compiles your project. - Cargo team

Cargo makes it easier to create, run, download, compile, test, and run your project. The cargo command internally calls the rustc compiler to execute the compilation.

Install Rust

So let's begin:

To install Rust with the rustup command.

In Linux or MacOs

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

The script will download the script file and runs it. The script will install the Rust language. Both rustc and cargo are installed in the ~/.cargo/bin and delegates any access to the underlying toolchain.

Once the installation is completed successfully, you can check the installation by running.

$ rustc --version
rustc 1.40.0 (73528e339 2019-12-16)

For Windows:

To install the Rustup, Download the Rustup from the binaries available here. Both rustc and cargo are installed in the users folder.

Note that you will require C++ build tools for Visual Studio 2013 or later. You can install them from here

Check the installation by running rustc --version

Rustup

The Rustup is a toolchain multiplexer. It installs and manages many Rust toolchains and proxies them through the single set of tools installed at .cargo/bin in the home directory.

Once the rustup command is installed, we can easily manage the rustc and cargo compilers. The rustup also makes it easy to switch between nightly, stable, and beta versions of Rust.

The Rust and WebAssembly is available in the Stable version of the Rust (version 1.30 and above). We will switch to the nightly build to make sure we get all the latest benefits.

To switch to the nightly version, we have to run

$ rustup default nightly

The above command will switch the default Rust compiler to the nightly version. The rustc proxy in the ~/.cargo/bin will run the nightly compiler instead of the stable compiler.

To update to the latest version of nightly we can run

$ rustup update

Once successfully updated, we can check the current version installed by running

$ rustc --version
rustc 1.42.0-nightly (da3629b05 2019-12-29)

Rust to WebAssembly compiler is built in with the default rustc compiler. This means the above toolchain setup is now capable of compiling your Rust code into WebAssembly modules. Let us see that in action.


Rust to WebAssembly

If you are thinking that the WebAssembly is best suited for the gaming applications, Online IDE, and other resource-intensive and memory hungry applications then you are technically correct. But we can reap the benefits of WebAssembly benefits in a simple application too.

For a normal application (i.e., applications that do not need ultra fast responses always) that we develop will have some parts that need performance tuning. Sometimes we have to remove that ugly spaghetti code that runs only on certain browsers and not on the others. WebAssembly provides an option here.

With Rust and WebAssembly, it is easy to patch those areas with faster, consistent WebAssembly code and use the application as such.

The tooling in the Rust for WebAssembly is the best at the moment. With Rust, you can either create an entire application with Rust (i.e., both the front and back end) or use Rust for a part of the application.

The Rust compiler uses LLVM underneath for converting the Rust source code into WebAssembly modules.

Read more about targets here

target wasm32-unknown-unknown

In order to create WebAssembly modules using Rust's toolchain we will be using the target wasm32-unknown-unknown.

Here the unknown-unknown instructs the compiler that the code can compile on any machine and run on any machine. This will create WebAssembly modules that will run on any machine.

We can install wasm32-unknown-unknown using

$ rustup target add wasm32-unknown-unknown

The wasm32-unknown-unknown target adds zero runtime and toolchain footprint. The former is because the target assumes only wasm32 instruction set is present. The latter is because you do not need a special compiler or toolchain or custom linker to compile.

Write some code ✍️

Create a new project with cargo.

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

A new project called car_fib is created. The --lib flag to inform the cargo to create a new library project rather than the default binary project.

The binary project will produce the executable or the actual binaries that will run. In our case, we are just building a library and then create WebAssembly modules out of it.

Spin up our favourite text editor and replace the contents of the src/lib.rs with the following.

#[no_mangle]
fn add(x: i32, y:i32) -> i32 {
    x + y
}

The #[no_mangle] annotation informs the compiler to not to mangle the names when generating the library.

We have to specify the compiler what type of crate we are compiling. We can specify the crate-type in the Cargo.toml file that is generated.

Open the Cargo.toml and add the crate-type information inside.

[package]
name = "car_fib"
version = "0.1.0"
authors = ["Sendil Kumar"]

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

The cdylib crate-type generates a dynamic system library. This dynamic system library is used when the library has to be loaded from another language.

Let us compile the Rust to WebAssembly module.

$ cargo build --target wasm32-unknown-unknown

The cargo build calls the rust compiler. The Rust compiler use the specified target wasm32-unknown-unknown. It generates the WebAssembly module inside /target/wasm32-unknown-unknown/.

Create an HTML file

$ touch index.html

Add the following contents in index.html:

<script>
 (async () => {
     const bytes = await fetch("target/wasm32-unknown-unknown/debug/car_fib.wasm");
     const response = await bytes.arrayBuffer();
     const result = await WebAssembly.instantiate(response, {});
     console.log(result.instance.exports.add(10,3));
 })();
</script>

We have used async-await here to make the syntax more elegant and contextually easier to understand.

First we fetch the WebAssembly module using the fetch. The bytes.arrayBuffer() returns the promise that resolves with an ArrayBuffer.

The WebAssembly.instantiate function allows you to compile and instantiate WebAssembly code. The resulting object have the exported methods.

As expected, this will print the output 13.

What has happened?

Here the cargo build command invokes rust compiler and compiles the Rust code to MIR (Middle Intermediate Representation) then into LLVM IR. The generated LLVM IR is then converted into WebAssembly module.

Alt Text

It is important to note that we did not use emscripten compiler here, everything is handled by the rust compiler itself.

Let us add another function. We can create a Fibonacci number generator with Rust and run them on the browser.

Open the car_fib.rs, replace everything with the following contents

#[no_mangle]
fn fibonacci(num: i32) -> i32 {
    match num {
        0 => 0,
        1 => 1,
        _ => fibonacci(num-1) + fibonacci(num-2),
    }
}

Build it using the cargo build --target wasm32-unknown-unknown.

Finally, replace the index.html such that we call the Fibonacci instead of the add.

<script>
 (async () => {
     const bytes = await fetch("target/wasm32-unknown-unknown/debug/car_fib.wasm");
     const response = await bytes.arrayBuffer();
     const result = await WebAssembly.instantiate(response, {});
     console.log(result.instance.exports.fibonacci(20));

 })();
</script>

Now spin up the HTML server and check the browser's console for the fibonacci value.

In the next blog let us see how to pass various values between JavaScript and WebAssembly module.


Repo


Interested to explore more...

More information about the linking here

You can check more about async-await here


🐦 Twitter // 💻 GitHub // ✍️ Blog

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