Your website has a bottleneck. A calculation module, a parsing function, a rendering algorithm that takes seconds in JavaScript and triggers the slowdown. The browser freezes, the user leaves, revenue drops. The solution isn't rewriting everything in a newer framework: it's moving the heavy code into WebAssembly compiled from Rust, with wasm-bindgen as the bridge. We do this every day for clients who need serious performance, not demos.
Why is Rust the best choice for WebAssembly in the browser?
JavaScript is everywhere, but it has a structural limit: garbage collection and dynamic typing make intensive computation slow. WebAssembly runs binary code at near-native speed. Rust, with its ownership system and zero-cost abstractions, compiles to WASM naturally, without a heavy runtime and with total control over memory.
Think of it this way: JavaScript is a convenient city car, Rust is a race engine. WebAssembly is the highway. You don't need a race engine for groceries, but if you need to process a 4K image or a 50 MB JSON file, the city car struggles.
We, at Meteora Web, have seen e-commerce projects slow down dramatically due to heavy client-side calculations written in JS. By migrating those modules to Rust+WASM, response time dropped from seconds to milliseconds. The result? More conversions, fewer bounces.
When does it make sense to use Rust for WebAssembly?
Not everything should be moved to WASM. It makes sense for:
Sponsored Protocol
- Intensive numerical computation (simulations, cryptography, compression)
- Parsing complex formats (PDF, binary files, large JSON)
- Real-time image, audio, and video processing
- Client-side machine learning algorithms
- Games and physics engines
If your problem is just a bit of slow DOM manipulation, stick with JavaScript. If the browser freezes, WASM is the answer.
How does communication between JavaScript and Rust work with wasm-bindgen?
wasm-bindgen is the key tool. It generates the glue between the two worlds: it lets you call Rust functions from JavaScript and vice versa, passing complex data like strings, arrays, and objects without writing FFI code by hand. It's the bridge that makes everything practical.
The flow is simple: write your Rust code, compile it with wasm-pack (which uses wasm-bindgen under the hood), and get a JavaScript module you can import like any other library. Rust functions become async, and types are converted automatically.
Your first project: setup and toolchain
You need rustup to manage the Rust toolchain and the wasm32-unknown-unknown target. Then install wasm-pack, which simplifies the build. Here are the commands:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup target add wasm32-unknown-unknown
cargo install wasm-pack
Now create a new project: cargo new --lib wasm-demo. Open Cargo.toml and add the dependencies:
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
The cdylib type is essential: it produces a dynamic library that can be loaded as a WASM module.
Sponsored Protocol
How to write Rust code that integrates with JavaScript?
The base is the #[wasm_bindgen] attribute. You apply it to functions, structs, and methods you want to expose to JavaScript. Here's a concrete example: a function that calculates the factorial, but recursively, to show Rust's power in pure computation.
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn factorial(n: u32) -> u64 {
match n {
0 | 1 => 1,
_ => (2..=n as u64).product(),
}
}
Compile with wasm-pack build --target web. This generates a pkg folder with the .wasm file, the JS glue module, and TypeScript types. In your HTML or JS module, you import it like this:
import init, { factorial } from './pkg/wasm_demo.js';
await init();
console.log(factorial(10)); // 3628800
Note: init() is asynchronous because it must load the .wasm file. This is the standard pattern with --target web.
Passing complex data: strings and arrays
wasm-bindgen automatically handles the conversion of base types like String, Vec<u8>, JsValue. For example, a function that receives a string and returns its length in bytes:
#[wasm_bindgen]
pub fn byte_length(input: &str) -> usize {
input.len()
}
In JavaScript, you call it normally: byte_length("hello") returns 5. Memory is copied, not shared, for safety. For large arrays, copying can be a cost, but for most cases it's acceptable. For extreme performance, shared memory techniques with SharedArrayBuffer exist, but they require specific HTTP headers and are more complex.
Sponsored Protocol
What is the cost in terms of performance and file size?
The WASM file isn't huge, but it's not tiny either. A simple module with wasm-bindgen weighs about 100-200 KB gzipped. For complex applications, it can exceed 1 MB. This impacts initial load time, but the advantage is much faster execution.
The trick is to balance: load the WASM module only when needed (lazy loading), not at page startup. With dynamic import(), you can do this easily:
async function loadHeavyModule() {
const module = await import('./pkg/wasm_demo.js');
await module.default();
return module;
}
This way, the browser downloads the WASM only when the user triggers the feature that requires it. The rest of the page stays light.
How to manage security and memory in Rust for WebAssembly?
Rust protects you from memory errors at compile time. This is a huge advantage over C or C++ compiled to WASM, where a pointer error can corrupt the browser's memory. With Rust, if it compiles, it's memory-safe.
Also, WASM runs in a sandbox: it has no direct access to the DOM or browser APIs. Everything goes through functions imported from JavaScript. This limits damage in case of bugs: a crash in WASM can't take down the whole page.
Sponsored Protocol
Memory management is explicit. Rust uses its own allocator for WASM, which you can configure. For applications that allocate and deallocate many small objects, the default allocator can be a bottleneck. In that case, you can use an allocator like wee_alloc or dlmalloc. We, at Meteora Web, solved performance issues in a 3D rendering project simply by changing the allocator.
Common mistakes to avoid
The most common is forgetting to call init() before using the functions. The second is trying to pass unsupported types directly. For example, you can't pass a Rust HashMap to JavaScript without serializing it to JSON. Use serde and serde-wasm-bindgen for automatic serialization:
[dependencies]
serde = { version = "1", features = ["derive"] }
serde-wasm-bindgen = "0.6"
#[derive(serde::Serialize)]
pub struct User {
name: String,
age: u8,
}
#[wasm_bindgen]
pub fn get_user() -> JsValue {
let user = User { name: "Mario".to_string(), age: 30 };
serde_wasm_bindgen::to_value(&user).unwrap()
}
In JavaScript, get_user() returns an object { name: "Mario", age: 30 } directly usable.
Which build tool should you choose for a Rust and WebAssembly project?
The simplest is wasm-pack, which handles building, testing, and publishing to npm. For more complex projects, with multiple crates or integrations with bundlers like webpack or Vite, you can use wasm-bindgen-cli directly or plugins like vite-plugin-wasm.
Sponsored Protocol
Our choice, when working on serious projects, is wasm-pack for its simplicity and compatibility with the npm ecosystem. It lets you publish your WASM module to npm with a single command: wasm-pack publish. This is a great advantage if you want to distribute your library to other developers.
For local development, wasm-pack watch recompiles automatically on every change, speeding up the development cycle.
What to do now
You have the tools to start. Here are the concrete actions:
- Install the toolchain:
rustup,wasm32-unknown-unknowntarget, andwasm-pack. - Create a test project: a simple Rust function, compile with
wasm-pack build --target web, and integrate it into an HTML page. - Measure performance: compare the execution time of the same function in JS and Rust/WASM with
performance.now(). You'll see the difference. - Identify a slow module on your site and evaluate moving it to WASM. Start with a small case, don't rewrite everything.
- Read the official documentation: wasm-bindgen guide is the starting point. For deeper insights, check the Rust and WebAssembly book.
If you want an opinion on how to integrate Rust and WebAssembly into your project, write to us. We, at Meteora Web, start with the numbers: how much does it cost and how much does it return? The rest comes after. And if you want to understand how Rust fits into a broader architecture, check out our main guide on Rust.