WebAssembly
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against Rust 1.99.0, wasm-bindgen 0.2.129 and the 2024 edition, checked on 2026-10-06. WebAssembly (Wasm) is a portable, sandboxed binary instruction format that browsers, server-side runtimes and plugin hosts can execute at near-native speed. Rust is one of the best-supported languages for it: no garbage collector or runtime to ship, small binaries, and first-class targets in the compiler. The examples below were built for each target and run in Node.js.
(no system interface)"] wasm1 --> bg["wasm-bindgen CLI
generates JS glue + .d.ts"] bg --> browser["Browser / bundler
(JS calls Rust, Rust calls DOM via web-sys)"] bg --> node["Node.js"] t2 --> wasm2["WASI module or component"] wasm2 --> host["WASI host: wasmtime, Node WASI,
edge platforms, plugin systems"]
Targets
| Target | Use |
|---|---|
|
no operating-system interface at all; the host supplies every import. Used for browsers (with wasm-bindgen) and for hosts with a custom API |
|
WASI preview 1: a POSIX-like system interface (files, clock, random, args, stdio). Formerly named
|
|
WASI preview 2 (0.2), built on the component model: produces a component with typed imports and exports described in WIT |
|
a minimal target restricted to the WebAssembly 1.0 feature set, for embedded and very old hosts |
rustup target add wasm32-unknown-unknown wasm32-wasip1 wasm32-wasip2
cargo build --release --target wasm32-wasip1 # target/wasm32-wasip1/release/shelf-wasi.wasm
Ordinary Rust compiles to these targets, with the obvious limits: no threads by default, no direct sockets in the browser,
and file access only through what the host provides (WASI). Crates that need the OS — tokio’s networking, most of
std::fs in the browser — do not work on wasm32-unknown-unknown.
WASI: command-line programs and the component model
A WASI program is a normal Rust binary. This one sorts and de-duplicates titles read from standard input:
use std::io::{self, BufRead, Write};
/// A WASI command: reads titles from stdin, writes the sorted, de-duplicated list to stdout.
fn main() -> io::Result<()> {
let mut titles: Vec<String> = io::stdin().lock().lines().collect::<Result<_, _>>()?;
titles.sort();
titles.dedup();
let mut out = io::stdout().lock();
for t in &titles {
writeln!(out, "{t}")?;
}
eprintln!("{} unique titles (compiled for {})", titles.len(), std::env::consts::ARCH);
Ok(())
}
Source: The rustc Book: wasm32-wasip1
Run it in any WASI host. With Node.js’s built-in WASI support:
import { readFile } from 'node:fs/promises';
import { WASI } from 'node:wasi';
const wasi = new WASI({ version: 'preview1', args: ['shelf-wasi'] });
const module = await WebAssembly.compile(await readFile('target/wasm32-wasip1/release/shelf-wasi.wasm'));
const instance = await WebAssembly.instantiate(module, wasi.getImportObject());
wasi.start(instance);
cargo build --release -p shelf-wasi --target wasm32-wasip1
printf 'Emma\nDune\nEmma\n' | node --no-warnings run-wasi.mjs
# Dune
# Emma
# 2 unique titles (compiled for wasm32)
Source: The rustc Book: wasm32-wasip1
With wasmtime, the Bytecode Alliance’s reference runtime: wasmtime run target/wasm32-wasip1/release/shelf-wasi.wasm.
Hosts grant capabilities explicitly — a WASI program sees only the directories and environment the host passes in
(wasmtime run --dir data::/data ...), which is what makes Wasm attractive for plugins and multi-tenant edge platforms.
Source: Wasmtime documentation
The component model (WASI 0.2, target wasm32-wasip2) goes further: components declare typed interfaces in WIT
(WebAssembly Interface Types), so a component written in Rust can be composed with one written in another language and
called with strings, records and lists rather than raw integers. wit-bindgen generates Rust bindings from WIT files, and
cargo component builds components from a WIT world.
Source: The WebAssembly Component Model
wasm-bindgen: Rust in the browser
wasm32-unknown-unknown modules can only exchange numbers with JavaScript. wasm-bindgen adds the rest: strings, objects,
classes, closures, promises and exceptions, by generating JavaScript glue code and TypeScript definitions from
#[wasm_bindgen] annotations. js-sys binds JavaScript built-ins (Array, Promise, Date) and web-sys binds every
Web API (DOM, fetch, Canvas, WebGL), each API behind a Cargo feature.
[package]
name = "shelf-wasm"
version = "0.1.0"
edition = "2024"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
wasm-bindgen = "0.2.129"
web-sys = { version = "0.3", features = ["console", "Document", "Element", "HtmlElement", "Window"] }
js-sys = "0.3"
Source: The wasm-bindgen Guide
use wasm_bindgen::prelude::*;
/// Exported to JavaScript as `isbnIsValid(text)`.
#[wasm_bindgen(js_name = isbnIsValid)]
pub fn isbn_is_valid(text: &str) -> bool {
let digits: Vec<u32> = text.chars().filter_map(|c| c.to_digit(10)).collect();
digits.len() == 13
&& digits.iter().enumerate().map(|(i, d)| d * if i % 2 == 0 { 1 } else { 3 }).sum::<u32>().is_multiple_of(10)
}
/// A Rust struct exposed as a JavaScript class.
#[wasm_bindgen]
pub struct Shelf {
titles: Vec<String>,
}
#[wasm_bindgen]
impl Shelf {
#[wasm_bindgen(constructor)]
pub fn new() -> Shelf {
Shelf { titles: Vec::new() }
}
pub fn add(&mut self, title: String) {
self.titles.push(title);
}
/// Returns a JavaScript array of matching titles.
pub fn search(&self, query: &str) -> js_sys::Array {
let q = query.to_lowercase();
self.titles.iter().filter(|t| t.to_lowercase().contains(&q)).map(|t| JsValue::from_str(t)).collect()
}
#[wasm_bindgen(getter)]
pub fn size(&self) -> usize {
self.titles.len()
}
}
impl Default for Shelf {
fn default() -> Self {
Self::new()
}
}
// Import a JavaScript function into Rust.
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(message: &str);
}
/// Runs when the module is instantiated: writes into the page through web-sys.
#[wasm_bindgen(start)]
pub fn start() -> Result<(), JsValue> {
log("shelf-wasm loaded");
let Some(document) = web_sys::window().and_then(|w| w.document()) else {
return Ok(()); // not in a browser (for example under Node.js)
};
if let Some(status) = document.get_element_by_id("status") {
status.set_text_content(Some("Catalogue ready"));
}
Ok(())
}
Source: The wasm-bindgen Guide
Generate the glue with the wasm-bindgen CLI, whose version must match the wasm-bindgen crate exactly:
cargo install wasm-bindgen-cli --version 0.2.129
cargo build --release --target wasm32-unknown-unknown -p shelf-wasm
wasm-bindgen --target web --out-dir pkg target/wasm32-unknown-unknown/release/shelf_wasm.wasm
ls pkg
# shelf_wasm.d.ts shelf_wasm.js shelf_wasm_bg.wasm shelf_wasm_bg.wasm.d.ts
<!doctype html>
<p id="status">Loading...</p>
<script type="module">
import init, { isbnIsValid, Shelf } from "./pkg/shelf_wasm.js";
await init(); // fetches and instantiates shelf_wasm_bg.wasm, runs #[wasm_bindgen(start)]
console.log(isbnIsValid("978-0-441-01359-3")); // true
const shelf = new Shelf();
shelf.add("Dune");
shelf.add("Dune Messiah");
console.log(shelf.search("messiah"), shelf.size); // ["Dune Messiah"] 2
</script>
The same build with --target nodejs produces a CommonJS module for Node.js, and --target bundler one for Vite or
webpack. Keep the boundary coarse: every call that passes strings copies them between JavaScript’s UTF-16 and Rust’s
UTF-8 in Wasm memory.
Packaging after the rustwasm sunset
The rustwasm GitHub organisation, home of wasm-pack, wasm-bindgen and the Rust and WebAssembly book, was sunset in
2025. wasm-bindgen moved to its own organisation and remains actively maintained (with its guide now at
wasm-bindgen.github.io); wasm-pack and the Rust and WebAssembly book are archived and should be treated as legacy.
Current options for packaging browser code:
-
the
wasm-bindgenCLI directly, as above, pluswasm-opt(from Binaryen) to shrink the binary; -
trunk, a build tool and dev server for Rust web applications; -
the build tooling of a Rust front-end framework (
cargo leptos,dxfor Dioxus); -
bundler plugins such as
vite-plugin-wasm.
Source: The wasm-bindgen Guide
Linear memory
A Wasm module has a linear memory: one contiguous, growable byte array that holds the Rust heap and stack. JavaScript
can see it as an ArrayBuffer, which is how wasm-bindgen copies strings in and out, and how large data (images, audio)
can be shared without copying by passing a pointer and a length. Memory grows in 64 KiB pages and never shrinks; the
default allocator is Rust’s dlmalloc port. Pointers are 32-bit (usize is 4 bytes) on wasm32; wasm64 targets with
64-bit memories exist but are not widely supported by hosts yet.
Size and performance
Release builds with opt-level = "s" or "z", lto = true and codegen-units = 1, followed by wasm-opt -Oz, are the
usual recipe for small binaries. Avoid pulling in std::fmt-heavy code paths and large dependencies; twiggy shows what
takes space. For CPU-bound work, Wasm usually runs within 1.5-2x of native speed; SIMD (target-feature=+simd128) and
threads (with SharedArrayBuffer, which requires cross-origin isolation in browsers) can narrow the gap.
Rust front-end frameworks
Frameworks such as Leptos, Dioxus and Yew let you write whole web front ends in Rust, compiled to Wasm, with server-side rendering and hydration. Leptos and Dioxus integrate with axum for full-stack applications; they are covered on Axum: Full-stack Rust with Leptos & Dioxus. Mara & Holmes (Refactoring to Rust, chapter 10) use Yew 0.19; current Yew (0.21+) and the newer frameworks have different APIs.
WebAssembly in other stacks
Other ecosystems on this site target WebAssembly too: Kotlin/Wasm in Kotlin Multiplatform (Web with Kotlin/Wasm & JS), .NET in the browser with Blazor (Blazor WebAssembly, Hybrid & Deployment), and machine-learning inference with Transformers.js and ONNX Runtime Web (Hugging Face: Web & Cross-Platform). Rust’s niche among them is small, GC-free modules that can be dropped into an existing JavaScript application.
Where the books are outdated
Mara & Holmes (Refactoring to Rust, chapters 9-10) target wasm32-wasi — renamed wasm32-wasip1 in Rust 1.78 and
removed in 1.84 — and use WasmEdge-specific forks (tokio_wasi, reqwest_wasi), wasm-bindgen 0.2.87 with wasm-pack,
and Yew 0.19. Today: use wasm32-wasip1 or wasm32-wasip2 (the component model), mainline crates where they support WASI,
the wasm-bindgen CLI or a framework’s tooling instead of the archived wasm-pack, and current framework versions.
References
-
The rustc Book: wasm32-unknown-unknown, wasm32-wasip1, wasm32-wasip2
-
Inside Rust Blog (announcement of the rustwasm organisation sunset, 2025)
-
Lily Mara and Joel Holmes, Refactoring to Rust (Manning, 2025)