Rust and WebAssembly: Compiling & vs JavaScript Guide

Max WellsMax WellsFounder of Rustify

TL;DR: WebAssembly (Wasm) is a binary instruction format that runs in browsers at near-native speed; it's a compile target alongside x86 and ARM. Rust is one of the best languages for Wasm because it produces tiny, fast binaries with no garbage collector pauses. wasm-bindgen connects Rust and JavaScript, enabling Rust functions to be called from JS and vice versa. Frameworks like Leptos and Yew let you write full web UIs entirely in Rust, compiled to Wasm.


What Is WebAssembly and Why Does Rust Excel at It?

WebAssembly is a binary format that all modern browsers can execute; it's a compilation target for languages like Rust, C, and C++ that runs in the same sandbox as JavaScript but significantly faster for compute-heavy work.

Rust source code
      ↓ rustc
  .wasm binary  ←  sandboxed, runs in browser

JavaScript calls Rust functions via wasm-bindgen

Rust is ideal for Wasm for three reasons:

  1. No garbage collector: GC pauses ruin interactive performance; Rust's ownership model eliminates them
  2. Tiny binaries: Rust produces smaller Wasm than Go or C++ with RTTI
  3. Direct compilation: Rust has first-class Wasm support with wasm32-unknown-unknown target

How Do You Compile Rust to WebAssembly?

Add the Wasm target, use wasm-pack to build and generate JS bindings, then import the generated module in your JavaScript.

# Install tools
rustup target add wasm32-unknown-unknown
cargo install wasm-pack
 
# Create a Wasm library
cargo new --lib my_wasm
# Cargo.toml
[lib]
crate-type = ["cdylib"]
 
[dependencies]
wasm-bindgen = "0.2"
// src/lib.rs
use wasm_bindgen::prelude::*;
 
#[wasm_bindgen]
pub fn add(a: u32, b: u32) -> u32 {
    a + b
}
 
#[wasm_bindgen]
pub fn greet(name: &str) -> String {
    format!("Hello, {}!", name)
}
# Build; generates pkg/ with .wasm and JS bindings
wasm-pack build --target web
// In your JavaScript / TypeScript
import init, { add, greet } from './pkg/my_wasm.js';
 
await init();
console.log(add(2, 3));      // 5
console.log(greet("Alice")); // "Hello, Alice!"

What Is wasm-bindgen?

wasm-bindgen is the bridge between Rust and JavaScript; it generates the glue code to call Rust from JS and JS from Rust, handling type conversions automatically.

use wasm_bindgen::prelude::*;
 
// Call JavaScript's console.log from Rust
#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(js_namespace = console)]
    fn log(s: &str);
}
 
// Expose a Rust struct to JavaScript
#[wasm_bindgen]
pub struct Counter {
    value: i32,
}
 
#[wasm_bindgen]
impl Counter {
    pub fn new() -> Self { Counter { value: 0 } }
    pub fn increment(&mut self) { self.value += 1; }
    pub fn get(&self) -> i32 { self.value }
}
const counter = Counter.new();
counter.increment();
counter.increment();
console.log(counter.get()); // 2

What Is Leptos and How Does It Use Wasm?

Leptos is a full-stack Rust web framework that compiles to WebAssembly for the client side; it enables interactive UIs written entirely in Rust, with the same codebase running server-side for SSR.

use leptos::prelude::*;
 
#[component]
fn Counter() -> impl IntoView {
    let (count, set_count) = signal(0);
 
    view! {
        <button on:click=move |_| set_count.update(|n| *n += 1)>
            "Count: " {count}
        </button>
    }
}

Leptos compiles to Wasm for the browser and to native code for SSR; the same component code runs in both environments. This is the same pattern used by Next.js but entirely in Rust.


When Should You Use Rust + Wasm Instead of JavaScript?

Rust + Wasm excels for compute-heavy browser tasks. For standard web UIs, JavaScript/TypeScript is simpler. The sweet spot is performance-critical components embedded in JS apps.

Use caseRecommendation
Image/video processing in browser✅ Rust + Wasm
Cryptography, hashing✅ Rust + Wasm
Game logic, physics simulation✅ Rust + Wasm
Audio processing / DSP✅ Rust + Wasm
Full UI framework (Leptos, Yew)✅ If team knows Rust
Standard CRUD web appReact/Vue/Svelte simpler
SEO-critical content siteHybrid (SSR + Wasm)

Frequently Asked Questions

For compute-intensive code (loops, math, bit manipulation), Rust Wasm typically runs 2–10× faster than equivalent JavaScript. For DOM manipulation, there is overhead in crossing the JS/Wasm boundary; batch DOM operations to minimize crossings.

Not natively; Wasm runs in a sandboxed environment and must call JavaScript to access the DOM. wasm-bindgen provides high-level bindings for document, window, and DOM APIs via the web-sys crate.

A minimal Rust Wasm binary is around 50–200 KB after wasm-opt optimization and gzip compression. The wee_alloc or dlmalloc allocators reduce size further. For comparison, the React runtime is ~130 KB gzipped.

Yes, via WebAssembly threads (SharedArrayBuffer + Atomics) in supporting browsers. Use wasm-bindgen-rayon for parallel iterators in Wasm. Browser support is good as of 2026, but requires COOP/COEP headers on the server.


Sources


  • Async/Await: Leptos uses async Rust for server functions
  • Trait: wasm-bindgen uses traits to define JS interop
  • Cargo: wasm-pack is built on top of Cargo
  • Ownership: No GC in Wasm (Rust's ownership model is why it works)
  • wasm-bindgen: The core interop layer between Rust-generated Wasm and JavaScript
  • Yew: Yew remains one of the most visible Rust frameworks built on top of the WebAssembly toolchain
  • Bevy: Bevy can target the browser through WebAssembly builds
  • PyO3: PyO3 is the main non-Wasm alternative when you want Rust to extend another runtime
  • Rustup: Rustup is how teams install additional WebAssembly compilation targets locally

Keep Reading

Ready to Land a $120k+ Rust Job in the US or Europe?