The Bull Case for Rust on the Web

Given what we do at Drifting in Space, we talk to a lot of developers pushing the limits of the browser. One trend I’ve noticed is teams building browser-based applications in Rust.

Desktop-class apps

To be clear, I’m not talking about traditional hypertext applications — blogs, social media, or e-commerce. I’m talking about applications that aspire to desktop-class status, but target the browser for low-friction distribution.

For example, a few weeks ago I wrote about applications that render UI directly on the GPU. Of the six mentioned apps that run (or aspire to run) in the browser, all of them are written in Rust.

Other applications often use JavaScript for UI and Rust for other functionality. The space design software Rayon is an example of this.

JavaScript as accidental bytecode

In a modern application, the JavaScript sent to the browser plays the role of bytecode. It’s not produced by hand, but by a compilation process. It’s generally not human-readable, either.

This is not how JavaScript was designed to be used. It’s the result of heroic engineering efforts to stretch JavaScript well beyond its original use case and handle the demands of large-scale applications.

For example, the industry has generally adopted TypeScript, so developers hand-annotate their code with types, which is good. But these types are only surface-level. Browser JavaScript does not understand type annotations, so they get stripped out.

Modern browsers do use type information to speed up JavaScript at runtime, but because type information is lost in translation, they have to start with a blank slate and observe the types that flow through the program as it runs.

So we are in a bizarre situation where programmers hand-annotate types, which get thrown away, and then browsers figure out what the types are at runtime.

It’s all a technical feat, but it adds a bunch of complexity.

Rather piling more complexity and fragility on top of JavaScript, the other path is to acknowledge that we’re treating JavaScript like bytecode, and create a purpose-built bytecode for the web.

That’s why WebAssembly was created.

Why Rust?

There are a number of languages that support WebAssembly. Rust stands out by being at the lonely intersection of languages that:

  1. Don’t use a garbage collector, and
  2. Have modern tooling/package ecosystem.

#1 matters because garbage collection is still WIP in Wasm. This generally rules out anything on the JVM as well as scripting languages.

It’s possible to compile a garbage collector to Wasm and bake it into the module, but this can bloat the module size and hamper runtime performance as the GC has to run in the main thread.

#2 is subjective, but Rust has great tooling. It’s easy to add dependencies to a wealth of libraries (crates), docs for all public crates are live in one place, and the language includes a test framework, formatter, and other tools.

Performance

WebAssembly is not a silver bullet for performance. Modern JavaScript interpreters do clever things to make applications run fast.

Along the happy path of applications typically built for the browser (think “typical React app”) and don’t use much CPU, this is fine.

When applications do need to optimize for performance, though, complexity comes back with a vengeance.

For example, V8 uses some heuristics to decide the underlying data structure to represent a JavaScript Array.

If you want to reason about what the browser is doing under the hood, you have to understand some magic that takes place in V8. And since it’s not part of the spec, those heuristics might vary between versions, let alone across other JavaScript engines.

The advantage of Rust+Wasm is not that you can sprinkle it on an app to get better performance, but that you can have a clear mental model of the performance characteristics of your code. You can know definitively how data will be laid out in memory, or how many memory allocations a block of code will make.

By combining this with a rich collection of standard data types, Rust provides a solid foundation for performant apps.

It’s worth noting that the JavaScript community itself has been embracing (non-Wasm) Rust for faster tooling. Turbopack and SWC both replace JavaScript-based tools and promise orders-of-magnitude speedups.

JavaScript will be fine

To be clear, JavaScript isn’t going anywhere. But as developers of performance-demanding apps increasingly target the browser as a platform, Rust is well-positioned to be their language of choice.

For more like this, subscribe to our Browsertech Digest or follow @JamsocketHQ on Twitter.