Rust in the browser¶
Level: 301 · deep dive
One line: A browser runs the .wasm module but only JavaScript can reach the page, so Rust in the browser is always Rust plus generated JavaScript glue — wasm-bindgen writes it — and every string that crosses is copied, and converted between UTF-8 and UTF-16, on the way.
Stub — an outline, not a lesson. There is no runnable example behind this page yet, so nothing on it has been through the check that backs every other claim in this library. The bullets below are the questions the finished page has to answer.
What it has to cover¶
- What
#[wasm_bindgen]generates on each side of the boundary, and why a module whose exports take and return only numbers needs no glue at all. - Strings across the boundary, per the
wasm-bindgenguide ↗:&strandStringare copied between JavaScript's garbage-collected heap and linear memory withTextEncoderandTextDecoder. JavaScript strings are UTF-16 and can hold unpaired surrogates, which becomeU+FFFDon the way into Rust.js_sys::JsStringis the handle that avoids the copy — what can Rust still do with it? js-sys↗ for JavaScript's built-ins andweb-sys↗ for the Web APIs — the DOM,fetch, canvas. Why doesweb-sysmake you switch on a Cargo feature for each API you use?- Calling JavaScript from Rust with an
externblock under#[wasm_bindgen], and the cost of each crossing: is a call into JavaScript cheap enough to make per pixel, per frame, per element? - The tooling as of 2026-09. The
rustwasmGitHub organisation was sunset (Inside Rust, 2025-07-21 ↗);wasm-bindgen↗ moved to its own organisation, and crates.io listswasm-pack0.15.0 underwasm-bindgen/wasm-pack. Trunk ↗ builds and serves anindex.htmlapp. Which of the three does a new project need? - The lowest-level path, as Bevy 0.19.1's examples README gives it:
cargo build --release --target wasm32-unknown-unknown, thenwasm-bindgen --target webon the resulting.wasm, then a static file server. - Panics:
panic="abort"is the target default, so what does a panic look like in the browser console — and what doesconsole_error_panic_hook↗ (last released in 2021; its GitHub repository is archived) add? - Frameworks on top — Leptos and Dioxus — are on Which Rust UI, and whether you want one; this page stops at the boundary they are built over.
The trap it exists for¶
Treating the JavaScript boundary as a function call. Passing a string back and forth in a loop copies and transcodes it every time, and the one piece of text a user pasted with a lone surrogate arrives in Rust silently changed to U+FFFD — no error on either side.
Where this sits¶
What WebAssembly is covers imports and exports in general and Compiling to wasm32 the target; Shipping a wasm page covers getting the files onto a server. This page covers only the Rust–JavaScript boundary inside the page.
See also¶
- Which Rust UI, and whether you want one — Leptos, Dioxus and Tauri, built on this boundary
- Four lengths, and which one the other system means — the same text measured as UTF-8 bytes in Rust and UTF-16 units in JavaScript
- STRINGS.md — the map of the library's string pages
- Calling C — the other boundary where "the call is free, the data is not"
stris unsized — why a&strtravels as a pointer and a length- Books — Rust and WebAssembly, listed there as dated in its tooling
- UTF-16 and surrogates ↗ — what an unpaired surrogate is
- Why UTF-16 stayed ↗ — why JavaScript's strings are still UTF-16
If you are coming from another language¶
- JavaScript. You already know the host. What changes is that the module's linear memory is a buffer you view from JavaScript as bytes (
WebAssembly.Memory'sbuffer), and a RustStringinside it is bytes that mean nothing to JavaScript until the glue decodes them. - Java.
charandStringare UTF-16 in Java exactly as in JavaScript, so the lone-surrogate problem is familiar — Acharis not a character ↗. Rust'sStringcannot hold a lone surrogate at all, which is why the conversion has to replace it rather than carry it. - Python. Pyodide runs CPython inside the page and documents its own type translations ↗ between Python and JavaScript values.
wasm-bindgenis the Rust equivalent, generated at build time, and its guide documents each conversion the same way — including the lossy one.
Po polsku¶
Przeglądarka uruchomi moduł .wasm, ale do strony (DOM) dostęp ma tylko JavaScript, więc Rust w przeglądarce to zawsze Rust plus wygenerowany kod klejący — pisze go wasm-bindgen. Każdy łańcuch znaków przechodzący przez tę granicę jest kopiowany i przekodowywany: w Ruscie to UTF-8, w JavaScripcie UTF-16, a niesparowany surogat zamienia się po cichu na U+FFFD. Narzędzia się przetasowały — organizacja rustwasm została wygaszona, wasm-bindgen ma własną — więc starsze samouczki warto czytać z tą poprawką.
Szukaj po polsku: Rust w przeglądarce · WebAssembly i JavaScript · wasm-bindgen strings utf-16 · web-sys features · rustwasm organization archived · trunk rust wasm