How to compile Rust into WebAssembly and run it in Deno

It’s an exciting time for web development right now as new technologies are being widely supported and adopted. We can now easily compile code written in the Rust language into the WebAssembly instruction format. We can then use it natively in web pages and also in JavaScript runtimes like Node.js, or, like we’ll do here, Deno.

Here’s the content we’ll cover, from installing the necessary tools to compile Rust code and the tools to compile it into the WebAssembly format, to finally loading and using the library in Deno. For this tutorial, we’ll assume a macOS environment.

What’s Rust, WebAssembly, and Deno?

Install Rust language tools

Install tools to compile to WebAssembly

Create the library

Load the library in Deno

What’s Rust, WebAssembly, and Deno?

Rust is a programming language focused on speed, memory safety, and parallelism. It’s open source and the main backer is Mozilla. It’s being used to build many things from game engines and operating systems to browser components and virtual reality engines. Mozilla actively uses it to build parts of the Firefox browser. Here’s a short intro video:

As cool as Rust is, it’s not something we can run directly in the browser or in a JavaScript runtime. Enter WebAssembly. It’s a binary instruction format for a stack-based virtual machine that aims to run in the browser at native speed and is sandboxed for security. The JavaScript API, that we’ll use later, provides a method to interact between the two.

And finally, Deno, the new JavaScript runtime, which is great to run WebAssembly because it has native support for its binaries and can use them following the JavaScript API mentioned before.

Install Rust language tools

Let’s begin by installing Rust following the installation page in its book:

$ curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh

This will install the compiler and also install Cargo. It’s a tool to manage dependencies and set some directives for compilation. Think of it as the package.json in a Node.js environment.

Now we just need to create a directory for our library. Cargo will take care of this for us:

cargo new rust-deno --lib

This will create a new directory rust-deno with a Cargo.toml file and a src directory, where you’ll find a lib.rs. This will be our library file that we’ll compile to WebAssembly and then load in Deno. Edit the file and replace its contents with:

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn reverse( text: &str ) -> String {
    text.split_whitespace().rev().collect::<Vec<&str>>().join( " " )
}

#[wasm_bindgen]
pub fn factorial( x: u64 ) -> u64 {
    match x {
        0 | 1 => 1,
        _ => x * factorial( x - 1 ),
    }
}

The first function will split a sentence by its whitespaces, reverse it, and join it back. The end of the `collect` function uses the turbofish notation to specify that the result will be a vector of strings. The second one will calculate the factorial for a number. Since these numbers grow up quickly, we’ll use the 64-bit unsigned integer type.

Great! Now we’re ready to compile these two functions to WebAssembly.

Install tools to compile to WebAssembly

Rust doesn’t compile directly to WebAssembly. We need to install a tool to build our Rust into WebAssembly. A popular one is wasm-pack but it looks like an abandoned project now, not having responded to issues since March. It doesn’t support Deno as a build target, although it does support Node.js as a target.

While we could load the .wasm file directly, there are some issues regarding translating Rust numbers to JavaScript numbers and string conversion issues that don’t make this practical. We’re going to use another tool called ssvmup that is also based on `wasm-pack` but specifically has Deno as a build target. Install it with:

curl https://raw.githubusercontent.com/second-state/ssvmup/master/installer/init.sh -sSf | sh

You now need to modify the Cargo.toml file a bit to specify a dependency: wasm-bindgen, which is what will allow us to compile to WASM:

[package]
name = "rust-deno"
version = "0.1.0"
authors = ["Your Name <your@email.tld>"]
edition = "2018"

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

[dependencies]
wasm-bindgen = "=0.2.61"

Create the library

Your project is now ready and you can now build the Rust library to WASM. Run the following in your terminal:

ssvmup build --target deno

The ssvmup tool will also generate a pkg directory where you’ll find a few files. Most interestingly, a WASM file, which is the compiled Rust code and a JavaScript file. this JS file will act as an exchange library between Deno and WASM. This is the file that you’ll later load in Deno. For example, in the case of Rust integers of type u64, the JS exchange file will convert them to BigInt so they’re understood by JS. This file will also provide JSDocs so you can have the function definition and typing in your IDE. This is how it looks in VS Code:

Deno code loading the WebAssembly library written in Rust

Load the library in Deno

Deno supports WebAssembly natively, and the exchange file already resolves that cleanly for us. We just need to import the functions as we would do from any other package and use them:

import { factorial, reverse } from './pkg/rust_deno.js';

console.log( factorial( BigInt( 20 ) ) );

console.log( reverse( 'this is fun' ) );

Note that as we pointed before, since our Rust code uses u64 integers, we need to use BigInt in JavaScript. If we had used a u32 integer, we could’ve just used a regular number in JavaScript, like `factorial( 5 )`.

And now we can run our code. We need to allow Deno to load the WebAssembly file so we’ll pass the `–allow-read` flag:

deno run --allow-read mod.ts

And that’s it! You just wrote Rust code, compiled it to WebAssembly, loaded the WASM file into Deno, and used the functions.

You can find the code for this in the following repository

https://github.com/eliorivero/rust-webassembly-deno

Leave a Reply