Rust WebAssembly (Wasm) with wasm-bindgen allows you to compile Rust code to run in the browser, interacting seamlessly with JavaScript.
Let’s see wasm-bindgen in action. Imagine you want to create a simple Rust function that adds two numbers and expose it to JavaScript.
First, you need a Rust project. Create one with cargo new rust_wasm_example --lib.
In your Cargo.toml, add the necessary dependencies:
[package]
name = "rust_wasm_example"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
The crate-type = ["cdylib"] is crucial; it tells Rust to build a dynamic library suitable for use with wasm-bindgen.
Now, in src/lib.rs, write your Rust function and use wasm_bindgen attributes to expose it:
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn add_numbers(a: i32, b: i32) -> i32 {
a + b
}
The #[wasm_bindgen] attribute tells wasm-bindgen to generate the necessary JavaScript glue code to make this function callable from the browser.
To build this for Wasm, you’ll use wasm-pack:
cargo install wasm-pack
wasm-pack build --target web
wasm-pack build --target web compiles your Rust code to Wasm and generates a pkg directory containing your Wasm module, JavaScript bindings, and a package.json.
Now, in your HTML file (index.html), you can load and use this Wasm module:
<!DOCTYPE html>
<html>
<head>
<title>Rust Wasm Example</title>
</head>
<body>
<script type="module">
import init, { add_numbers } from './pkg/rust_wasm_example.js';
async function run() {
await init();
const result = add_numbers(5, 10);
console.log(`The sum is: ${result}`); // Output: The sum is: 15
}
run();
</script>
</body>
</html>
The import init is generated by wasm-bindgen and is necessary to initialize the Wasm module. Once initialized, you can directly import and call your Rust functions like add_numbers.
This system solves the problem of running computationally intensive tasks or leveraging existing Rust libraries directly within a web browser environment, bridging the gap between native performance and web accessibility. wasm-bindgen handles the complex interop: translating Rust data types to JavaScript equivalents and vice versa, managing memory, and even enabling callbacks from JavaScript to Rust.
The mental model is that your Rust code is compiled into a binary format (Wasm) that the browser understands. wasm-bindgen acts as a translator, creating a JavaScript "shim" that knows how to load the Wasm, pass arguments to your Rust functions, and return results. You’re essentially writing a dynamic library for the web.
The levers you control are primarily within your Rust code:
- Data Types:
wasm-bindgensupports many primitive types (integers, floats, booleans, strings) and more complex ones likeVec<T>,Option<T>, andResult<T, E>. For custom structs and enums, you can use#[wasm_bindgen(inspectable)]or#[wasm_bindgen(typescript_custom_section)]for more advanced scenarios. - Function Signatures: How you define your Rust functions (arguments, return types) directly dictates how they are exposed to JavaScript.
- Attributes:
#[wasm_bindgen]is the most common, but others like#[wasm_bindgen(js_name = ...)]allow you to rename functions for JavaScript consumers,#[wasm_bindgen(module = ...)]to import JavaScript functions into Rust, and#[wasm_bindgen(start)]to run code on Wasm initialization. - Error Handling: Rust’s
Resulttype is automatically translated into JavaScript Promises that either resolve with the success value or reject with an error, a very idiomatic pattern for asynchronous operations in JavaScript.
The most surprising thing is how deeply wasm-bindgen can integrate with JavaScript, allowing you to pass complex JavaScript objects directly into Rust functions and have them represented as Rust types, and vice-versa, without manual serialization. For instance, you can pass a JavaScript ArrayBuffer to a Rust function expecting a &[u8] slice, and wasm-bindgen handles the zero-copy transfer.
This deep integration extends to event handling. You can write Rust code that directly registers event listeners on DOM elements, written in idiomatic Rust using closures and JsCast for type safety when interacting with the DOM API.
The next concept you’ll likely encounter is managing complex state and sharing it between Rust and JavaScript, often involving Rc<RefCell<T>> or more advanced techniques for persistent data structures.