Skip to content

Rust and wasm-bindgen

Rust is the most popular language for writing new WebAssembly modules. Its zero-cost abstractions, no garbage collector, and excellent tooling make it a natural fit. The wasm-bindgen crate generates the JavaScript glue code automatically, and wasm-pack wraps the whole build pipeline into a single command.

Terminal window
# Install Rust (if not already installed)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Add the WebAssembly target to the Rust toolchain
rustup target add wasm32-unknown-unknown
# Install wasm-pack — the all-in-one Rust → Wasm build tool
cargo install wasm-pack
Terminal window
# Create a library crate (not a binary)
cargo new --lib hello-wasm
cd hello-wasm

Add wasm-bindgen to Cargo.toml:

[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"

Open src/lib.rs and write a simple exported function:

use wasm_bindgen::prelude::*;
/// Returns a greeting string.
#[wasm_bindgen]
pub fn greet(name: &str) -> String {
format!("Hello, {}! from WebAssembly", name)
}
/// Adds two 32-bit integers.
#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
a + b
}

The #[wasm_bindgen] attribute tells the code generator to create matching JavaScript bindings. Without it, the function is compiled into the .wasm binary but is not accessible from JS.

Terminal window
# Build for use in a bundler (webpack, Vite, Rollup)
wasm-pack build --target bundler
# Build for direct use in a browser via <script type="module">
wasm-pack build --target web
# Build for Node.js
wasm-pack build --target nodejs

The pkg/ directory is created with:

pkg/
hello_wasm_bg.wasm ← the compiled Wasm binary
hello_wasm.js ← generated JS glue
hello_wasm.d.ts ← TypeScript types
package.json ← ready to publish on npm
// When using --target web (no bundler required)
import init, { greet, add } from './pkg/hello_wasm.js';
async function main() {
// init() downloads and instantiates the .wasm file
await init();
console.log(greet('world')); // Hello, world! from WebAssembly
console.log(add(3, 4)); // 7
}
main();

When using a bundler such as Vite, the import is identical but init() is handled automatically:

// Vite / webpack — bundler handles instantiation
import { greet, add } from 'hello-wasm';
console.log(greet('WebAssembly'));

wasm-bindgen handles more than just numbers. You can pass:

  • &str / String — UTF-8 strings are copied through a shared memory buffer
  • JsValue — an opaque handle to any JavaScript value
  • Vec<u8> — byte arrays mapped to Uint8Array
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn reverse_string(s: &str) -> String {
s.chars().rev().collect()
}
What does the #[wasm_bindgen] attribute do in Rust?
Which wasm-pack target flag should you use to call a module directly from a browser <script type="module"> without a bundler?
What type does wasm-bindgen use to represent an opaque JavaScript value in Rust?
Which crate-type is required in Cargo.toml for a Wasm library?