WebAssembly Basics
10 examples introducing WebAssembly and Rust's wasm32 compilation targets.
Prerequisites
- Rust 1.97.0 with
wasm32-unknown-unknowntarget installed - Basic familiarity with Cargo and modules
rustup target add wasm32-unknown-unknownBasic Examples
1. Hello WASM Module
// src/lib.rs
#[no_mangle]
pub extern "C" fn add(a: i32, b: i32) -> i32 {
a + b
}cargo build --target wasm32-unknown-unknown --release- WASM exports functions to JavaScript hosts
#[no_mangle]keeps symbol names stable- Output is
.wasmbinary intarget/wasm32-unknown-unknown/release/ - No
mainfunction in library-style modules
2. wasm32 vs wasm32-wasi
| Target | Host environment |
|---|---|
wasm32-unknown-unknown | Browsers via wasm-bindgen |
wasm32-wasi | WASI runtimes (Wasmtime, Node) |
Choose the target matching your deployment environment.
3. Release Profile for Size
[profile.release]
opt-level = "s"
lto = true
strip = true
codegen-units = 1opt-level = "s"optimizes for size over speed- LTO removes dead code across crates
- WASM download size matters in browsers
- Pair with
wasm-optfor further shrinking
4. #![no_std] WASM
#![no_std]
#[panic_handler]
fn panic(_: &core::panic::PanicInfo) -> ! {
loop {}
}
#[no_mangle]
pub extern "C" fn double(x: i32) -> i32 {
x * 2
}- Embedded-style WASM without allocator
- Required for smallest modules
- No
StringorVecwithout an allocator crate - Common in game engines and plugins
5. Logging from WASM
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
pub fn info(msg: &str) {
log(msg);
}- Import JavaScript functions into Rust
console.logis the simplest debug path- Prefer
web_sys::consolein real projects - Remove debug imports in production builds
Related: wasm-bindgen
6. Panic Hook for Browsers
use console_error_panic_hook;
use wasm_bindgen::prelude::*;
#[wasm_bindgen(start)]
pub fn init() {
console_error_panic_hook::set_once();
}- Rust panics become readable browser console errors
- Call once at module init
- Essential for development; optional in prod
- Works with
#[wasm_bindgen(start)]
7. Cargo.toml for WASM Crates
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
wasm-bindgen = "0.2"cdylibproduces the.wasmartifactrliballows Rust integration tests on native target- Keep dependencies WASM-compatible (no native OS crates)
Intermediate Examples
8. Inspect WASM Output
wasm-objdump -x target/wasm32-unknown-unknown/release/mylib.wasm
wasm2wat target/wasm32-unknown-unknown/release/mylib.wasm | headwasm-objdumpshows exports, imports, and sizeswasm2watdisassembles to text format for debugging- Install via
wasm-toolsor WABT - Check export names match JS glue expectations
9. Minimal HTML Loader
<script type="module">
const { default: init, add } = await import("./pkg/mylib.js");
await init();
console.log(add(2, 3));
</script>- Modern browsers load WASM as ES modules
wasm-packgenerates the JS glueinit()async-loads the.wasmfile- Serve with correct
Content-Type: application/wasm
Related: wasm-pack & Bundling
10. When Not to Use WASM
- Heavy DOM manipulation (JavaScript may be simpler)
- Direct filesystem access in browsers (use APIs or WASI)
- Tiny scripts where JS bundle is already loaded
Rust WASM shines for compute-heavy work: parsers, codecs, crypto, and simulations.
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.