Rust wasm-bindgen
wasm-bindgen is the translator that makes crossings between Rust and JavaScript safe and boring. Text, objects, and calls in both directions all need to cross.
Numbers cross for free
Whole numbers, floats, and booleans mean the same thing on both sides:
let n: i32 = 7; // a JavaScript number
let ok: bool = true; // a JavaScript boolean
let pi: f64 = 3.5; // a JavaScript numberText crosses as a copy
Text is copied across in both directions. JavaScript never points inside Rust memory:
#[wasm_bindgen]
pub fn shout(word: &str) -> String {
word.to_uppercase()
}A struct becomes a class
Mark the struct and its impl. JavaScript creates the object with new. It calls inc three times, then reads the count:
#[wasm_bindgen]
pub struct Counter { count: i32 }
#[wasm_bindgen]
impl Counter {
#[wasm_bindgen(constructor)]
pub fn new() -> Counter { Counter { count: 0 } }
pub fn inc(&mut self) { self.count += 1; }
pub fn value(&self) -> i32 { self.count }
}Rust calls JavaScript back
Declare the JavaScript function in an extern block. The js_namespace part names where it lives:
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
#[wasm_bindgen]
pub fn say_hello() {
log("hello from Rust");
}Code that runs at load
The start attribute marks the setup function. It runs once, when the module loads:
#[wasm_bindgen(start)]
fn start() {
log("the module has started");
}Failure crosses as an exception
Return a Result to report failure. The Err value becomes a thrown exception on the JavaScript side:
#[wasm_bindgen]
pub fn parse_age(text: &str) -> Result<u32, JsValue> {
text.parse().map_err(|_| JsValue::from_str("not a number"))
}Example
// Cargo.toml: [lib] crate-type = ["cdylib", "rlib"]
// Cargo.toml: [dependencies] wasm-bindgen = "0.2"
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
#[wasm_bindgen]
pub struct Counter {
count: i32,
}
#[wasm_bindgen]
impl Counter {
#[wasm_bindgen(constructor)]
pub fn new() -> Counter {
Counter { count: 0 }
}
pub fn inc(&mut self) {
self.count += 1;
}
pub fn value(&self) -> i32 {
self.count
}
}
#[wasm_bindgen]
pub fn shout(word: &str) -> String {
word.to_uppercase()
}
#[wasm_bindgen]
pub fn say_hello() {
log("hello from Rust");
}
#[wasm_bindgen(start)]
fn start() {
log("the module has started");
}Key points
- wasm-bindgen: a crate and a tool that translate values between Rust and JavaScript at the border
- Easy crossings: numbers, booleans, strings, Vec of numbers, Option, and Result
- A marked struct crosses as a JavaScript class: JavaScript creates it and calls its methods
- References with lifetimes, generics, and trait objects cannot cross — design the border with plain data
- Use an extern block to declare JavaScript functions, so Rust code can call them back
- Every crossing copies text and can allocate.
- Real work needs more than one small function across the border.
In this example, one small crate shows every crossing from this lesson. The extern block declares console.log, a JavaScript function. Counter is a plain Rust struct with three methods. The start function runs by itself at load time, so its line prints first. A JavaScript host then builds a Counter, calls inc three times, and prints the count: 3. It calls shout with "ferris" and prints FERRIS. Last, say_hello calls back into JavaScript, and the console shows hello from Rust. Rust drove the page's own console the whole time.
Note
Keep border calls chunky: one big call beats a thousand tiny ones.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
Which value cannot cross the wasm-bindgen border as it is?