Rust and WebAssembly
17.1 Targets
Browsers/JS hosts (with wasm-bindgen), no OS assumptions WASI Preview 1 (CLI tools, runtimes) WASI 0.2 components Interop with Emscripten wasm64-unknown-unknown (tier 3)
17.2 wasm-bindgen and wasm-pack
cargo new --lib wasm_demo && cd wasm_demo# Cargo.toml
[package]
name = "wasm_demo"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
wasm-bindgen = "0.2"
js-sys = "0.3"
web-sys = { version = "0.3", features = ["console", "Window", "Document", "Element", "HtmlElement"] }
serde = { version = "1", features = ["derive"] }
serde-wasm-bindgen = "0.6"
console_error_panic_hook = "0.1"
[profile.release]
opt-level = "z" # optimize for size (or 3 for speed)
lto = true
codegen-units = 1
panic = "abort"// src/lib.rs
use serde::{Deserialize, Serialize};
use wasm_bindgen::prelude::*;
#[wasm_bindgen(start)]
pub fn init() {
console_error_panic_hook::set_once(); // readable panic messages in the console
}
#[wasm_bindgen]
pub fn greet(name: &str) -> String {
format!("Hello, {name}!") // strings handled automatically
}
#[wasm_bindgen]
pub fn sum(values: &[f64]) -> f64 {
values.iter().sum() // typed arrays ↔ slices
}
#[wasm_bindgen]
pub struct Counter {
value: i32,
}
#[wasm_bindgen]
impl Counter {
#[wasm_bindgen(constructor)]
pub fn new(start: i32) -> Counter { Counter { value: start } }
pub fn increment(&mut self) { self.value += 1; }
#[wasm_bindgen(getter)]
pub fn value(&self) -> i32 { self.value }
}
#[derive(Serialize, Deserialize)]
pub struct Point { x: f64, y: f64 }
#[wasm_bindgen]
pub fn midpoint(a: JsValue, b: JsValue) -> Result<JsValue, JsValue> {
let a: Point = serde_wasm_bindgen::from_value(a)?;
let b: Point = serde_wasm_bindgen::from_value(b)?;
Ok(serde_wasm_bindgen::to_value(&Point { x: (a.x + b.x) / 2.0, y: (a.y + b.y) / 2.0 })?)
}
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_name
…wasm-pack build --target web # also: bundler, nodejs, deno, no-modules
wasm-pack build --release --target bundler
wasm-pack test --headless --chrome # tests in a real browser
wasm-pack publish # publish to npmimport init, { greet, sum, Counter, midpoint } from "./pkg/wasm_demo.js";
await init();
console.log(greet("Ana"), sum(new Float64Array([1, 2, 3])));
const c = new Counter(1); c.increment(); console.log(c.value); c.free();
console.log(midpoint({ x: 0, y: 0 }, { x: 4, y: 2 }));17.3 Async, Closures and Promises
Crates: gloo (ergonomic web APIs), wasm-bindgen-rayon (threads), getrandom (needs the wasm_js backend config), instant/web-time, tsify (TypeScript types).
use wasm_bindgen_futures::JsFuture;
use web_sys::{Request, RequestInit, Response};
#[wasm_bindgen]
pub async fn fetch_text(url: String) -> Result<JsValue, JsValue> {
let window = web_sys::window().unwrap();
let resp: Response = JsFuture::from(window.fetch_with_str(&url)).await?.dyn_into()?;
JsFuture::from(resp.text()?).await // returns a JS Promise to callers
}
// Closures passed to JS
let cb = Closure::<dyn FnMut()>::new(move || web_sys::console::log_1(&"tick".into()));
window.set_interval_with_callback_and_timeout_and_arguments_0(cb.as_ref().unchecked_ref(), 1000)?;
cb.forget(); // leak intentionally for long-lived callbacks (or store it)17.4 Rust for WASI
// src/main.rs
use std::{env, fs};
fn main() {
let args: Vec<String> = env::args().collect();
println!("args: {args:?}");
let text = fs::read_to_string("/data/input.txt").unwrap_or_default();
println!("chars: {}", text.chars().count());
}cargo build --target wasm32-wasip2 --release
wasmtime run --dir ./data::/data target/wasm32-wasip2/release/app.wasm