This tutorial takes you through all you need to know to compile a Rust project to WebAssembly and then run it in Deno.
Why Rust? 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 (Just In Time Compiler) 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.
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. Before we go any further, the following need to be installed on your machine for working with Rust and Deno.
- rustc: This is the compiler for the Rust programming language, provided by the project itself. Compilers take your source code and produce binary code, either as a library or executable.
- rustup: This installs the Rust programming language from the official release channels, enabling you to easily switch between stable, beta and nightly compilers and keep them updated.
- cargo: This is the Rust package manager. Cargo downloads your Rust package’s dependencies, compiles your packages, makes distributable packages and uploads them to crates.io, the Rust community’s package registry.
- deno: This is a JavaScript/TypeScript runtime with secure defaults and a great developer experience.
Set up WebAssembly for Rust
Now, we need to install the following WebAssembly tools for Rust.
rustup target add wasm32-unknown-unknown
cargo install wasm-gc
- wasm32-unknown-unknown: The
wasm32-unknown-unknown
target represents a WebAssembly output that makes zero assumptions about its environment, hence theunknown-unknown
. The target is intended to encapsulate use cases that do not rely on any imported functionality. The binaries generated are entirely self-contained by default when using the standard library. - wasm-gc: A small tool to garbage collect a WebAssembly module and remove all unneeded exports, imports, functions, etc.
Note: If you are using Visual Studio Code for development, install the following extensions.
- Better Toml
bungcip.better-toml
- Rust
rust-lang.rust
Create a Cargo Lib
First, we need to open Visual Studio Code in our workspace. Next, we need to create a small cargo lib. Open the terminal and run the following command:
cargo new --lib wasm_deno_example
cd wasm_deno_example
Next, open the Cargo.toml
file and add the dependencies for wasm.
//Cargo.toml
[lib]
crate-type =["cdylib"]
Note: cdylib
makes our project usable with other languages like C or in our case wasm
. It also removes all the specific stuff that is needed for rust.
Create a Rust Function
Paste the following code in the src/lib.rs
file.
#[no_mangle]
pub extern "C" fn age(cy: i32, yob: i32) -> i32{
cy - yob
}
This is a simple function that takes in the current year and your year of birth and returns your age.
Note: We added the extern
keyword so that this function can be imported into our Deno code.
Compiling the Rust to WebAssembly
Now, we are ready to compile our rust code to wasm code. Run the following code to build it.
$ cargo build --target wasm32-unknown-unknown
Next using the wasm-gc
, we need to remove all unneeded exports, imports, functions, etc.
wasm-gc target/wasm32-unknown-unknown/debug/wasm_deno_example.wasm
Now, we have a wasm
binary ready to be loaded into Deno and executed.
Load and Execute Binary with Deno
Create a main.ts
file.
touch main.ts
Add the following code to the file.
//main.ts
const wasmCode = await Deno.readFile("./target/wasm32-unknown-unknown/debug/wasm_deno_example.wasm");
const wasmModule = new WebAssembly.Module(wasmCode);
const wasmInstance = new WebAssembly.Instance(wasmModule);
const {
age,
} = wasmInstance.exports;
const yourAge = age(2020, 1994)
console.log(yourAge);
The above code loads the raw file. Then it makes a wasm module out of our file so that we can work with it. Next, it creates an instance of our module so that we can use the functions after which we imported our wasm
age()
function into our Deno code.
To execute the code run the following.
deno run --allow-read main.ts
$ 26
Conclusion
And that’s it! You just wrote Rust code, compiled it to WebAssembly, loaded the WASM file into Deno and used the functions.