TechLead
Lesson 21 of 28
5 min read
Rust

Rust & WebAssembly

Compile Rust to WebAssembly with wasm-pack and wasm-bindgen, call Rust from JavaScript, and build high-performance browser applications.

Rust and WebAssembly

WebAssembly (WASM) lets you run compiled code in the browser at near-native speed. Rust is one of the best languages for WASM thanks to its small runtime, no garbage collector, and excellent tooling with wasm-pack and wasm-bindgen.

When to Use Rust + WASM

  • Performance-critical code: Image/video processing, physics simulations, crypto
  • Existing Rust libraries: Reuse server-side logic in the browser
  • CPU-intensive tasks: Data parsing, compression, ML inference
  • Gaming: Game engines and real-time rendering

Setting Up

# Install wasm-pack
cargo install wasm-pack

# Or via curl:
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh

# Create a new WASM project
cargo new --lib wasm-hello
cd wasm-hello
// Cargo.toml
// [lib]
// crate-type = ["cdylib"]
//
// [dependencies]
// wasm-bindgen = "0.2"
// js-sys = "0.3"
// web-sys = { version = "0.3", features = ["console", "Document", "Element", "HtmlElement", "Window"] }

use wasm_bindgen::prelude::*;

// Export a function to JavaScript
#[wasm_bindgen]
pub fn greet(name: &str) -> String {
    format!("Hello, {name} from Rust + WASM!")
}

#[wasm_bindgen]
pub fn fibonacci(n: u32) -> u64 {
    match n {
        0 => 0,
        1 => 1,
        _ => {
            let mut a: u64 = 0;
            let mut b: u64 = 1;
            for _ in 2..=n {
                let temp = a + b;
                a = b;
                b = temp;
            }
            b
        }
    }
}

// Use JavaScript APIs from Rust
#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(js_namespace = console)]
    fn log(s: &str);
}

#[wasm_bindgen]
pub fn init() {
    log("WASM module initialized!");
}

Building and Using in JavaScript

# Build the WASM package
wasm-pack build --target web       # For ES modules
wasm-pack build --target bundler   # For webpack/Vite
wasm-pack build --target nodejs    # For Node.js
// JavaScript usage (ES modules)
// <script type="module">
// import init, { greet, fibonacci } from './pkg/wasm_hello.js';
//
// async function main() {
//     await init();
//     console.log(greet("World"));
//     console.log("fib(40) =", fibonacci(40));
//
//     // Performance comparison
//     const start = performance.now();
//     const result = fibonacci(45);
//     const elapsed = performance.now() - start;
//     console.log("WASM fib(45):", result, "in", elapsed, "ms");
// }
// main();
// </script>

DOM Manipulation from Rust

use wasm_bindgen::prelude::*;
use web_sys::{Document, Element, HtmlElement, Window};

#[wasm_bindgen]
pub fn create_element(tag: &str, text: &str) -> Result<(), JsValue> {
    let window: Window = web_sys::window().unwrap();
    let document: Document = window.document().unwrap();
    let body: HtmlElement = document.body().unwrap();

    let element: Element = document.create_element(tag)?;
    element.set_text_content(Some(text));
    element.set_attribute("class", "rust-created")?;

    body.append_child(&element)?;
    Ok(())
}

#[wasm_bindgen]
pub fn update_counter(id: &str, value: i32) -> Result<(), JsValue> {
    let document = web_sys::window().unwrap().document().unwrap();
    if let Some(element) = document.get_element_by_id(id) {
        element.set_text_content(Some(&value.to_string()));
    }
    Ok(())
}

Practical WASM Use Cases

use wasm_bindgen::prelude::*;

// Image processing in WASM
#[wasm_bindgen]
pub fn grayscale(pixels: &mut [u8]) {
    // pixels is RGBA data from a canvas
    for chunk in pixels.chunks_exact_mut(4) {
        let r = chunk[0] as f32;
        let g = chunk[1] as f32;
        let b = chunk[2] as f32;
        let gray = (0.299 * r + 0.587 * g + 0.114 * b) as u8;
        chunk[0] = gray;
        chunk[1] = gray;
        chunk[2] = gray;
        // chunk[3] is alpha — leave unchanged
    }
}

// JSON parsing benchmark
#[wasm_bindgen]
pub fn parse_large_json(data: &str) -> Result {
    let value: serde_json::Value = serde_json::from_str(data)
        .map_err(|e| JsValue::from_str(&e.to_string()))?;
    let count = count_fields(&value);
    Ok(count)
}

fn count_fields(value: &serde_json::Value) -> u32 {
    match value {
        serde_json::Value::Object(map) => {
            map.len() as u32 + map.values().map(count_fields).sum::()
        }
        serde_json::Value::Array(arr) => {
            arr.iter().map(count_fields).sum()
        }
        _ => 0,
    }
}

Key Takeaways

  • ✅ Rust compiles to small, fast WASM binaries with no GC overhead
  • wasm-pack and wasm-bindgen bridge Rust and JavaScript seamlessly
  • ✅ WASM excels at CPU-intensive tasks: image processing, crypto, parsing, simulations
  • web-sys provides typed access to browser DOM APIs from Rust
  • ✅ Build targets: web (ES modules), bundler (webpack/Vite), or nodejs

Continue Learning