Skip to content

Use Rust in JavaScript through wasm-pack

Open in GitHub Codespaces

wasm-pack is a great tool that automagically lifts and lowers complex structs, enums, strings, etc. back and forth across the WebAssembly boundary using wasm-bindgen via the #[wasm_bindgen] macro. It also can generate a self-contained npm package that you can use directly or distribute via npmjs.com.

This all means that you can write a library in Rust that can then be consumed by JavaScript using the magic of WebAssembly.

Let's get started by making sure that our Cargo.toml is configured correctly:

toml
[package]
name = "mylib"
version = "0.1.0"
edition = "2021"

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

[dependencies]
chrono = "0.4.31"
wasm-bindgen = "0.2.90"

crate-type = ["cdylib"] tells the Rust compiler to generate a dynamic library. In this case, that means a .wasm file without a start function.

📚 Read more on the wasm-pack getting started guide

📖 Did you know? dep-name = "1.2.3" in the Cargo world is equivalent to "dep-name": "^1.2.3" in the npm world. All version specifiers are ^ caret-ranged by default. You specify exact versions in Cargo.toml using =1.2.3 instead.

Now that we have our Rust package configuration out of the way lets move on to the actual Rust code that we want to turn into WebAssembly:

rs
use chrono::{Datelike, TimeZone, Utc};
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub struct Person {
    #[wasm_bindgen(getter_with_clone)]
    pub name: String,
    pub birth_date: i64, // Unix seconds
}

#[wasm_bindgen]
impl Person {
    pub fn compute_age(&self) -> i32 {
        let birth_date = Utc.timestamp_opt(self.birth_date, 0).unwrap();
        let current_date = Utc::now();
        current_date.year() - birth_date.year()
    }
}

#[wasm_bindgen]
pub fn create_alan_turing() -> Person {
    Person {
        name: String::from("Alan Turing"),
        birth_date: -1815328800, // June 23, 1912
    }
}

🕒 We're using the chrono Rust crate to do some time-related operations here.

Look at all those #[wasm_bindgen] macros! They're doing the heavy lifting here of transforming the struct, impl functions, and fn arguments & return value into WebAssembly pointers and back again. When you run wasm-pack build Cargo will compile the Rust code with all the juicy WebAssembly wrapper code generated by #[wasm_bindgen] and wasm-pack will also generate a nice mylib.js file that handles all the JavaScript-side conversions for you too! It even generates a reasonably good .d.ts file for TypeScript autocompletion.

Now is a good time to start thinking about the JavaScript side of this Rust & JavaScript story.

js
import init, { create_alan_turing, Person } from "./pkg/mylib.js";
await init();

const alanTuring = create_alan_turing();
console.log(alanTuring);
console.log(alanTuring.compute_age());
html
<script type="module" src="index.js"></script>
<p>Check the DevTools console!</p>

Now that we have our Rust code and our JavaScript code, lets run it to see it work:

Look at that 🧙‍♂️ we're magically using some Rust-defined functions and struct objects from JavaScript! 😱 This is a very basic example. wasm-bindgen (the magic macro) also supports callbacks, constructors, getters & setters, and more!