Rust, compiled to JavaScript

Three panels of #![no_std] Rust compiled to plain JavaScript by a rustc codegen backend: no WASM, no bundler. The browser loads /demo/app.js as an ordinary script and calls exported functions by name.

The same compiler also drives the DOM: the counter island is server rendered markup that compiled Rust takes over in the browser, and the nesting island does it through two component boundaries, and the searching island asks the server for its contents.

What that compiler is for is on the showcase: a board that plays itself, sand that falls under the pointer, and a minefield that floods open from a click. Three programs, none of which the expression language that came before could have said at all.

This toggle is Topcoat's own expression runtime: a signal, a few hundred bytes of generated script. The panels below are the other approach, a whole Rust crate compiled ahead of time. Both coexist on one page and neither knows about the other.

Life

The boards are JavaScript arrays. Rust receives them as &[bool] and &mut [bool], which the backend represents as { buf, off, len } over the array itself. Nothing is copied and nothing is serialized: the compiled code indexes the same array the page holds.

generation 0, population 0. Click a cell to toggle it.

Formatting

Every keystroke calls compiled core::fmt. Width and precision are runtime values, but fill and alignment are compile time: core::fmt bakes them into the format string, so the Rust side is a matrix of write! arms. Floats are missing on purpose: Display for f64 is not implemented by the backend yet.

Panics

The location below is a Rust file, line, and column, read from PanicInfo inside the compiled crate's #[panic_handler]. The two unwrap choices report different lines because of a single #[track_caller] attribute.

Stopping in the Rust source

  1. Open DevTools and go to the Sources panel.
  2. Find demo-app/client/fail.rs. The source map embeds the Rust text, so it is really there.
  3. Turn on "Pause on caught exceptions".
  4. Run a panicking choice. The debugger stops on the unwrap() line in Rust, and the call stack names panic_demo and unwrap_here. The compiler's own glue is hidden, because the map lists it in its ignoreList.

The page keeps working after a panic. State is owned by JavaScript and the exported functions are pure, so a panic is an exception, not corruption.