Integrating Rust with WebAssembly and JavaScript: A Step‑by‑Step Guide
This guide explains how to set up a Rust toolchain, compile Rust code to WebAssembly, use wasm‑bindgen and wasm‑pack, write the necessary Rust and JavaScript glue code, and run the resulting Wasm module in a web page with a Web Worker, providing a complete workflow for front‑end developers.
Purpose
The article aims to demonstrate a workflow for developing multi‑language programs using Rust, WebAssembly, JavaScript, HTML, and CSS; to design APIs that leverage the strengths of Rust/Wasm and JavaScript; and to show how to debug Wasm modules compiled from Rust.
What Is WebAssembly?
WebAssembly (Wasm) is a portable, compact binary format that runs at near‑native speed. It has a text representation .wat (S‑expression based) and a binary representation .wasm . Tools exist to convert between the two.
Environment Preparation
Install the standard Rust toolchain (rustup, rustc, cargo) following the official instructions at rust-lang.org/tools/install .
Learning Resources
Refer to the official Rust‑WebAssembly documentation and the wasm‑bindgen website for further reading.
Demo Project
The demo builds a Rust + JavaScript project that creates a WebAssembly module interacting with a Web Worker to determine whether a user‑entered number is even.
1. Install wasm‑pack
cargo install wasm-pack2. Create a new Rust library
cargo new --lib my_demo
cd my_demo3. Configure Cargo.toml
[package]
authors = ["The wasm-demo Developers"]
edition = "2024"
name = "wasm-in-web-worker"
publish = false
version = "0.0.0"
[lib]
crate-type = ["cdylib"]
[dependencies]
console_error_panic_hook = { version = "0.1.6", optional = true }
wasm-bindgen = "0.2"
web-sys = { version = "0.3", features = ['console','Document','HtmlElement','HtmlInputElement','MessageEvent','Window','Worker'] }4. Write Rust code (src/lib.rs)
// Import necessary crates
use std::cell::RefCell;
use std::rc::Rc;
use wasm_bindgen::prelude::*;
use web_sys::{console, HtmlElement, HtmlInputElement, MessageEvent, Worker};
#[wasm_bindgen]
pub struct NumberEval { number: i32 }
#[wasm_bindgen]
impl NumberEval {
pub fn new() -> NumberEval { NumberEval { number: 0 } }
pub fn is_even(&mut self, number: i32) -> bool { self.number = number; self.number % 2 == 0 }
pub fn get_last_number(&self) -> i32 { self.number }
}
#[wasm_bindgen]
pub fn startup() {
let worker_handle = Rc::new(RefCell::new(Worker::new("./worker.js").unwrap()));
console::log_1(&"Created a new worker from within Wasm".into());
setup_input_oninput_callback(worker_handle);
}
fn setup_input_oninput_callback(worker: Rc
>) {
let document = web_sys::window().unwrap().document().unwrap();
let mut persistent_callback_handle = get_on_msg_callback();
let closure = Closure::new(move || {
console::log_1(&"oninput callback triggered".into());
let document = web_sys::window().unwrap().document().unwrap();
let input = document.get_element_by_id("inputNumber").expect("#inputNumber should exist").dyn_ref::
().expect("#inputNumber should be a HtmlInputElement");
match input.value().parse::
() {
Ok(num) => {
let worker_ref = &*worker.borrow();
let _ = worker_ref.post_message(&num.into());
persistent_callback_handle = get_on_msg_callback();
worker_ref.set_onmessage(Some(persistent_callback_handle.as_ref().unchecked_ref()));
}
Err(_) => {
document.get_element_by_id("resultField").expect("#resultField should exist").dyn_ref::
().expect("#resultField should be a HtmlInputElement").set_inner_text("");
}
}
});
document.get_element_by_id("inputNumber").expect("#inputNumber should exist").dyn_ref::
().expect("#inputNumber should be a HtmlInputElement").set_oninput(Some(closure.as_ref().unchecked_ref()));
closure.forget();
}
fn get_on_msg_callback() -> Closure
{
Closure::new(move |event: MessageEvent| {
console::log_2(&"Received response: ".into(), &event.data());
let result = if event.data().as_bool().unwrap() { "even" } else { "odd" };
let document = web_sys::window().unwrap().document().unwrap();
document.get_element_by_id("resultField").expect("#resultField should exist").dyn_ref::
().expect("#resultField should be a HtmlInputElement").set_inner_text(result);
})
}5. Build the project
wasm-pack build --target no-modules6. Use in a web page
Create index.html that loads the generated .wasm and JavaScript glue code.
<html>
<head>
<meta charset="utf-8" />
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="wrapper">
<h1>Interaction with Wasm Web Worker</h1>
<input type="text" id="inputNumber">
<div id="resultField"></div>
</div>
<script src='./pkg/wasm_in_web_worker.js'></script>
<script src="./index.js"></script>
</body>
</html>Write index.js to load the Wasm module and call startup() :
// index.js
const { startup } = wasm_bindgen;
async function run_wasm() {
await wasm_bindgen();
console.log('index.js loaded');
startup();
}
run_wasm();Write worker.js to receive numbers, evaluate evenness using the Rust struct, and post the result back:
// worker.js
importScripts('./pkg/wasm_in_web_worker.js');
console.log('Initializing worker');
const { NumberEval } = wasm_bindgen;
async function init_wasm_in_worker() {
await wasm_bindgen('./pkg/wasm_in_web_worker_bg.wasm');
const num_eval = NumberEval.new();
self.onmessage = async event => {
const result = num_eval.is_even(event.data);
self.postMessage(result);
};
}
init_wasm_in_worker();7. Serve the files
Start a simple HTTP server, e.g., python3 -m http.server , and open http://localhost:8000 in a browser.
Application Scenarios
Rust + WebAssembly can be used for performance‑critical tasks, game development, cryptography, IoT/edge computing, desktop apps via Electron/Tauri, file compression, real‑time communication, custom renderers, porting existing Rust libraries, and as a safer alternative to legacy plugins.
Conclusion
The guide provides a practical workflow for integrating Rust‑compiled WebAssembly into JavaScript projects, highlighting performance, type safety, memory safety, concurrency, modern toolchains, ecosystem growth, and cross‑platform compatibility, while also noting learning‑curve and tooling challenges.
JD Tech Talk
Official JD Tech public account delivering best practices and technology innovation.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.