🎓 A1Learner
🔍

Rust in the Browser

web-sys is the set of bindings that lets Rust reach the page itself. The browser sets firm limits on what any of this may do.

Start from the window

Every page task starts at the window. web-sys mirrors the JavaScript names:

let window = web_sys::window().expect("no window");
let document = window.document().expect("no document");

Add a heading to the page

Each step can fail, so the steps return Options and Results. This is the shape of all DOM work in Rust:

let body = document.body().expect("no body");
let heading = document.create_element("h1")?;
heading.set_text_content(Some("Hello from Rust!"));
body.append_child(&heading)?;

APIs arrive feature by feature

Forget a feature and the method simply does not exist. The compile error names the missing piece:

// Cargo.toml
[dependencies.web-sys]
version = "0.3"
features = ["Window", "Document", "Element", "HtmlElement", "Node"]

App projects build with Trunk

Framework projects skip wasm-pack. Trunk builds the app, serves it, and rebuilds when files change:

// in your terminal:
// $ trunk serve
// builds the app and serves it locally

Heavy work leaves the main thread

A long Rust call freezes the page, just like a long JavaScript call. Browsers give heavy work its own place:

// std::thread cannot spawn a page thread here.
// Long jobs belong in a Web Worker, or in small
// slices that hand control back to the page.

Example

// Cargo.toml: [lib] crate-type = ["cdylib", "rlib"]
// Cargo.toml: [dependencies] wasm-bindgen = "0.2"
// Cargo.toml: [dependencies.web-sys] version = "0.3"
// Cargo.toml: features = ["Window", "Document", "Element", "HtmlElement", "Node"]
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn add_heading() -> Result<(), JsValue> {
    let window = web_sys::window().expect("no window");
    let document = window.document().expect("no document");
    let body = document.body().expect("no body");
    let heading = document.create_element("h1")?;
    heading.set_text_content(Some("Hello from Rust!"));
    body.append_child(&heading)?;
    Ok(())
}

Key points

  • web-sys: machine-generated bindings to the browser APIs, from window down to single elements
  • Each browser API hides behind a Cargo feature with its own name: no feature, no method
  • Leptos and Yew are Rust frameworks that build whole pages from components and compile to WebAssembly
  • Trunk is the app tool for those frameworks: it builds the project and serves it while you work
  • The browser sandbox is firm: no std::net, no file system, and no spare threads.
  • Network and storage arrive as browser APIs.
  • Ship release builds to the web.
  • The tiny greeter crate in these lessons packs to about 22 KB in release.

In this example, one exported function does the whole DOM job. It asks web-sys for the window, then the document, then the body. Each step can fail, so the code expects success or stops with a clear message. It creates an h1 element and sets its text. The question mark sends any failure back to JavaScript as an exception. The last step appends the heading to the page. One honesty note: this program runs in a page, not in a terminal. Its code was compile-checked for the wasm32 target. The output line is the text of the heading the page gains.

Note

A raw debug build of it is over 2 MB.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

A Rust Wasm app needs to save data on the user's machine. What should it use?