Rust and WebAssembly in 2026: Wasm, wasm-bindgen and Interview Questions
Complete guide to Rust WebAssembly development with wasm-pack and wasm-bindgen. Learn to build, optimize, and test Wasm modules with practical examples and interview questions.

Rust WebAssembly (Wasm) brings near-native performance to web applications by compiling Rust code to a binary format that runs in browsers and server environments. The combination of Rust's memory safety guarantees with WebAssembly's sandboxed execution creates a powerful foundation for performance-critical web components, from image processing to cryptographic operations.
Rust compiles to WebAssembly through wasm-pack and wasm-bindgen, producing modules that JavaScript can import and call directly. The workflow involves writing Rust functions, annotating them with #[wasm_bindgen], compiling to .wasm, and importing the generated JavaScript glue code into any web application.
Setting Up Rust for WebAssembly Development
The Rust toolchain requires the wasm32-unknown-unknown target and wasm-pack for building WebAssembly modules. These tools handle compilation, optimization, and JavaScript binding generation automatically.
# install-wasm-tools.sh
# Add the WebAssembly target to rustup
rustup target add wasm32-unknown-unknown
# Install wasm-pack for building and packaging
cargo install wasm-pack
# Verify installation
wasm-pack --versionAfter installation, create a new library crate configured for WebAssembly output. The Cargo.toml file requires specific settings for Wasm compatibility.
# Cargo.toml
[package]
name = "wasm-calculator"
version = "0.1.0"
edition = "2024"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
wasm-bindgen = "0.2.100"
[profile.release]
opt-level = "z" # Optimize for size
lto = true # Link-time optimizationThe cdylib crate type produces a dynamic library suitable for WebAssembly. The release profile optimizations reduce the final .wasm file size significantly—often by 50% or more compared to unoptimized builds.
Understanding wasm-bindgen and JavaScript Interop
The wasm-bindgen crate bridges Rust and JavaScript by generating type-safe bindings automatically. Functions annotated with #[wasm_bindgen] become callable from JavaScript, while JavaScript functions can be imported into Rust.
use wasm_bindgen::prelude::*;
// Export this function to JavaScript
#[wasm_bindgen]
pub fn fibonacci(n: u32) -> u32 {
// Base cases for recursion
match n {
0 => 0,
1 => 1,
// Recursive calculation with tail-call optimization
_ => fibonacci(n - 1) + fibonacci(n - 2),
}
}
// Import console.log from JavaScript
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
// Export a function that uses console.log
#[wasm_bindgen]
pub fn greet(name: &str) {
log(&format!("Hello, {}!", name));
}The extern "C" block declares JavaScript functions that Rust code can call. The js_namespace attribute specifies the JavaScript object containing the function. This bidirectional binding enables seamless integration between Rust logic and JavaScript APIs.
Building and Bundling WebAssembly Modules
The wasm-pack build command compiles Rust to WebAssembly and generates JavaScript wrapper code. Different targets suit different JavaScript environments.
# build-wasm.sh
# Build for bundlers like webpack, Vite, or Rollup
wasm-pack build --target bundler --release
# Build for native ES modules (browser)
wasm-pack build --target web --release
# Build for Node.js
wasm-pack build --target nodejs --releaseEach target produces different output in the pkg/ directory. The bundler target works with modern JavaScript bundlers that support ES modules and WebAssembly imports. The web target produces code that loads directly in browsers via <script type="module">.
// Import the generated WebAssembly module
import init, { fibonacci, greet } from './pkg/wasm_calculator.js';
async function run() {
// Initialize the WebAssembly module
await init();
// Call exported Rust functions
const result = fibonacci(20);
console.log(`fibonacci(20) = ${result}`);
greet('WebAssembly');
}
run();The init() function fetches and compiles the .wasm file asynchronously. After initialization, exported functions behave like regular JavaScript functions, with wasm-bindgen handling type conversions automatically.
Ready to ace your Rust interviews?
Practice with our interactive simulators, flashcards, and technical tests.
Working with Complex Types and Memory
WebAssembly operates on linear memory—a contiguous block of bytes. Passing complex types between Rust and JavaScript requires serialization or direct memory manipulation. The wasm-bindgen crate handles most common types automatically.
use wasm_bindgen::prelude::*;
use serde::{Serialize, Deserialize};
// Enable serde support for wasm-bindgen
#[derive(Serialize, Deserialize)]
pub struct Point {
pub x: f64,
pub y: f64,
}
// Accept and return JavaScript objects
#[wasm_bindgen]
pub fn distance(a: JsValue, b: JsValue) -> Result<f64, JsValue> {
// Deserialize JavaScript objects to Rust structs
let point_a: Point = serde_wasm_bindgen::from_value(a)?;
let point_b: Point = serde_wasm_bindgen::from_value(b)?;
// Calculate Euclidean distance
let dx = point_b.x - point_a.x;
let dy = point_b.y - point_a.y;
Ok((dx * dx + dy * dy).sqrt())
}
// Return a Rust struct as a JavaScript object
#[wasm_bindgen]
pub fn midpoint(a: JsValue, b: JsValue) -> Result<JsValue, JsValue> {
let point_a: Point = serde_wasm_bindgen::from_value(a)?;
let point_b: Point = serde_wasm_bindgen::from_value(b)?;
let mid = Point {
x: (point_a.x + point_b.x) / 2.0,
y: (point_a.y + point_b.y) / 2.0,
};
// Serialize Rust struct to JavaScript object
Ok(serde_wasm_bindgen::to_value(&mid)?)
}The serde-wasm-bindgen crate (add to Cargo.toml: serde-wasm-bindgen = "0.6" and serde = { version = "1.0", features = ["derive"] }) converts between JavaScript objects and Rust types efficiently. For performance-critical code, direct memory access via js_sys and web_sys crates avoids serialization overhead.
Optimizing WebAssembly Performance and Size
WebAssembly modules benefit from several optimization techniques. Size reduction improves load times, while algorithmic optimizations improve runtime performance. The wasm-opt tool from Binaryen provides additional post-compilation optimizations.
# optimize-wasm.sh
# Install binaryen for wasm-opt
# On macOS: brew install binaryen
# On Ubuntu: apt install binaryen
# Apply aggressive size optimizations
wasm-opt -Oz -o pkg/wasm_calculator_opt.wasm pkg/wasm_calculator_bg.wasm
# Apply speed optimizations instead
wasm-opt -O3 -o pkg/wasm_calculator_fast.wasm pkg/wasm_calculator_bg.wasmAdditional Cargo.toml settings further reduce output size:
# Cargo.toml - additional optimizations
[profile.release]
opt-level = "z"
lto = true
codegen-units = 1
panic = "abort"
strip = trueThese settings produce the smallest possible output by enabling link-time optimization, reducing parallel code generation, and stripping debug symbols. A simple Wasm module can shrink from 100KB to under 20KB with proper optimization.
Practical Use Case: Image Processing in the Browser
WebAssembly excels at computationally intensive tasks like image processing. The following example applies a grayscale filter to image data, demonstrating direct memory access for maximum performance.
use wasm_bindgen::prelude::*;
use wasm_bindgen::Clamped;
#[wasm_bindgen]
pub fn grayscale(data: Clamped<Vec<u8>>, width: u32, height: u32) -> Clamped<Vec<u8>> {
let mut pixels = data.0;
let len = (width * height * 4) as usize;
// Process each pixel (RGBA format)
for i in (0..len).step_by(4) {
// Calculate luminance using standard coefficients
let r = pixels[i] as f32;
let g = pixels[i + 1] as f32;
let b = pixels[i + 2] as f32;
let gray = (0.299 * r + 0.587 * g + 0.114 * b) as u8;
// Set RGB channels to grayscale value
pixels[i] = gray; // R
pixels[i + 1] = gray; // G
pixels[i + 2] = gray; // B
// Alpha channel (i + 3) remains unchanged
}
Clamped(pixels)
}The JavaScript side extracts pixel data from a canvas, passes it to Wasm, and renders the result:
import init, { grayscale } from './pkg/image_processor.js';
async function applyGrayscale(canvas) {
await init();
const ctx = canvas.getContext('2d');
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
// Pass pixel data to WebAssembly
const result = grayscale(imageData.data, canvas.width, canvas.height);
// Create new ImageData with processed pixels
const processed = new ImageData(result, canvas.width, canvas.height);
ctx.putImageData(processed, 0, 0);
}This approach processes millions of pixels per second—significantly faster than equivalent JavaScript code. Benchmarks on a typical laptop show 3-5x speedup for image operations.
Interview Questions: Rust WebAssembly Concepts
Understanding Rust WebAssembly concepts demonstrates systems programming knowledge. These questions appear frequently in interviews for performance-focused web development roles.
Q: What is the relationship between wasm-bindgen and JavaScript interoperability?
wasm-bindgen generates JavaScript glue code that handles type conversions between Rust and JavaScript. It creates a JavaScript wrapper for each exported Rust function, managing memory allocation, string encoding, and complex type serialization. Without wasm-bindgen, developers would need to manually handle the WebAssembly linear memory and implement their own serialization protocols.
Q: Why does WebAssembly use linear memory, and what are its implications?
WebAssembly's linear memory is a contiguous, resizable array of bytes that both JavaScript and Wasm code can access. This design provides memory safety through bounds checking and sandboxing. The implication is that passing complex data structures requires either copying data into linear memory or using references with careful lifetime management. Rust's ownership model aligns well with this constraint, as it prevents dangling pointers and data races at compile time.
Q: How does Rust's no_std mode benefit WebAssembly development?
The #![no_std] attribute excludes the Rust standard library, producing smaller Wasm binaries. Without std, developers use core and alloc crates for basic functionality. This approach reduces bundle size dramatically—from ~100KB to ~10KB for simple modules. The tradeoff is losing conveniences like String, Vec, and standard I/O, though alloc provides heap-allocated types when needed.
For deeper exploration of Rust async patterns that complement WebAssembly development, see the async/await module questions.
Debugging and Testing WebAssembly Modules
Effective testing requires both Rust-side unit tests and browser integration tests. The wasm-pack test command runs tests in a headless browser environment.
#[cfg(test)]
mod tests {
use super::*;
use wasm_bindgen_test::*;
// Configure tests to run in browser
wasm_bindgen_test_configure!(run_in_browser);
#[wasm_bindgen_test]
fn test_fibonacci() {
assert_eq!(fibonacci(0), 0);
assert_eq!(fibonacci(1), 1);
assert_eq!(fibonacci(10), 55);
}
#[wasm_bindgen_test]
fn test_greet() {
// This test verifies greet doesn't panic
greet("Test User");
}
}Run browser tests with Chrome or Firefox:
# test-wasm.sh
# Run tests in headless Chrome
wasm-pack test --headless --chrome
# Run tests in headless Firefox
wasm-pack test --headless --firefoxFor debugging, enable source maps and console output. The console_error_panic_hook crate provides meaningful panic messages in the browser console:
use wasm_bindgen::prelude::*;
#[wasm_bindgen(start)]
pub fn init_panic_hook() {
console_error_panic_hook::set_once();
}Add to Cargo.toml: console_error_panic_hook = "0.1". This setup displays Rust panic messages in browser developer tools instead of cryptic WebAssembly errors.
Conclusion
- Rust compiles to WebAssembly via
wasm-pack, producing modules callable from JavaScript with automatic type conversions - The
#[wasm_bindgen]attribute marks functions for export;extern "C"blocks import JavaScript functions into Rust - Size optimizations (
opt-level = "z",lto = true,wasm-opt -Oz) reduce bundle size by 50-80% - Complex types pass through
serde-wasm-bindgenfor JSON-like serialization or direct memory access for maximum performance - Image processing, cryptography, and compute-heavy algorithms see 3-5x speedups compared to JavaScript implementations
- Browser testing with
wasm-pack test --headlessvalidates both Rust logic and JavaScript integration
For related Rust topics, explore the Cargo & Ecosystem interview questions and the complete Rust interview preparation guide.
Start practicing!
Test your knowledge with our interview simulators and technical tests.
Tags
Share
Related articles

Rust Smart Pointers Explained: Box, Rc, Arc and RefCell in 2026
Rust smart pointers Box, Rc, Arc and RefCell explained with compilable 2026 examples, a decision table and common interview questions.

Rust Traits and Generics in 2026: Trait Upcasting, AsyncFn and Advanced Patterns
Master Rust traits and generics with the latest 2024 Edition features: trait upcasting, AsyncFn closures, RPITIT, and advanced patterns tested in real interviews.

Async/Await in Rust: Tokio, Futures and Asynchronous Concurrency Explained
Rust async/await deep dive covering Tokio runtime, Futures trait, task spawning, structured concurrency, and real-world patterns for building high-performance asynchronous applications.