@radekmie

On Rust in Webdev

By Radosław Miernik · Published on · Comment on Reddit

Table of contents

Intro

I remember that a couple of years back, we everybody laughed at the idea of “everything in JavaScript”. It was right after the great success of Emscripten. We imagined that literally everything will make it into a web browser: desktop applications, heavy graphics processors, or even AAA games.

Today, with WebAssembly, we are a thousand steps closer to this goal. As it becomes more and more powerful, it makes sense to take other languages into consideration while working on a web application. Adobe announced that they managed to squeeze Photoshop into the browser – that’s huge!

I believe there’s no need for an introduction of Rust. According to the Stack Overflow’s Annual Developer Survey, it’s the most loved language for six years straight. As such, it gained an enormous community, including some passionate web developers. Some of them focus on rewriting everything in Rust1; others began mixing it more reasonably, under the hood, as an implementation detail.

That’s why we see JavaScript-centric projects being rewritten. Parcel switched to an swc-based JS transformer, achieving 4-fold improvements. Recently they announced their new CSS transformer that is 100x faster. Rome switched to Rust even before it got finished. All of these lead to better and faster tooling.

How about your project?

Tooling

Setting up a new project for a full-stack developer like me is a tedious task. Of course, I can use one of the existing boilerplate projects, but none of them ever felt right to me. There’s always a package (or twenty) that I wouldn’t use, two that are missing. And I’d have to adjust most of the configuration files anyway. But I do it only once in a while, so maintaining a template doesn’t really make sense – it’d get outdated by the time I’d need it again.

When it comes to Rust, basically everything I need is already included1. Need a code formatter? cargo fmt. Linter? cargo clippy. Test runner? cargo test. Benchmark harness? cargo bench. Documentation generator? cargo doc. There are over 30 cargo commands in total. Of course, not all of them are necessarily unique, and they have some limits, but hey – they are official.

I was amazed when I first saw Go’s go fmt, or Elixir’s mix docs. You say that everyone is using it? Count me in! Rust, as (most) modern languages (including Nim), does the same. Yet, the tools it provides are of excellent quality2.

And last, but definitely not least, the compiler itself. Yes, it’s infamously slow (although it’s getting better with each version), but the resulting programs are fast. Also, compilation errors are very informative, although they require some practice (especially when it comes to lifetimes). Just look how helpful it is:

error: `<` is interpreted as a start of generic arguments for `usize`, not a comparison
 --> src/example.rs:4:28
  |
4 |     if node_count as usize < visited {
  |                            ^ --------- interpreted as generic arguments
  |                            |
  |                            not interpreted as comparison
  |
help: try comparing the cast value
  |
4 |     if (node_count as usize) < visited {
  |        +                   +

Packages

I can imagine how different life of a C++ developer coming to Rust is when adding any external code is as simple as cargo install – no more copying code by hand (or even worse: using git submodule), problems with versions, or integrating build systems. It’s far more familiar for JavaScript and Python programmers, as we do use npm or pip on a daily basis.

In terms of numbers, there are more than 75k crates on crates.io right now. It’s not that many if we compare it to npm (1.85m) or PyPI (352k), but it’s still way more than one would ever need. Quality-wise, I’d say it’s strictly better – most of them have documentation and test suites (of course, using the official tools).

The most popular packages include rand (random number generation), syn (Rust source code parser; it’s used for implementing advanced macros), serde ((de)serialization framework), and bitflags (bitset data structure macro). This sample shows that some features that we may consider standard are not part of the language or even the standard library.

The standard library itself is growing slowly but steadily. Basically every single release stabilizes new APIs. And if you’d like to try the non-stable ones, just switch to a beta or nightly release. Coming from Node.js, it’s much faster – new APIs are introduced on average once a month. The best part is that “quality of life” functions are added regularly (not like String.prototype.replaceAll).

I have to make one honorable mention here. For my master thesis, I spent a couple of days reimplementing the Nim engine for Legends of Code and Magic. Both engines were already fast, but I wanted to make the new one parallelized. In Nim, I used the parallel section to magically parallelize the work. I looked for a similar thing in Rust, and I found rayon. It took me literally one line to make the code automatically span across all of the available CPUs.

Ergonomics

I do like functional programming and everything that is common in these languages – algebraic data types (with pattern matching), implicit returns, if expressions (plus if let and while let), using Options instead of nulls, and Result instead of throw/catch. I find it easier to read and understand. Rust is an imperative language, but it reads like a functional one (mostly).

If we combine it with the beforementioned tooling, excellent packages, and active development, we have everything that is needed for a perfect language. Additionally, the fact that we may use macros to extend the language pushes the boundaries even further. Do you like JSX? All you need is a single package. There’s no need for a separate compilation step or a compiler extension.

Macros, while powerful, are relatively hard (conceptually). However, the trait system is the single best feature of Rust to me. You can compare them to interfaces in TypeScript, but there’s a twist – you can implement a trait for all of the existing types. It drastically changes the way you think about the methods but is surprisingly handy3.

Also, do you like async/await syntax for asynchronous code? You can do it in Rust too! What is more, the code is automatically verified for correctness. All data types must be explicitly marked as thread-safe, using Send and Sync traits (or both). That sounds like a tedious task, but it actually makes us think about multithreading sooner than ever.

It’s also important how easy integrating Rust with other languages is. There’s the ffi for C-like compatibility, wasm-pack for WebAssembly, and Neon for creating Node.js addons. With all of these, we can replace basically every web developer tool with Rust4. We can do it feature-by-feature!

Popularity

The popularity of a language is not a major factor for me – I have successfully delivered a couple of projects in Elixir, Nim, and even Pony. The fact that Rust does have a large community is a great addition, though. Every single problem I have ever encountered has already been asked (and answered!) on Stack Overflow or resolved in a GitHub issue. There’s also a very active forum.

The popularity is not limited to programmers – there are a lot of (big) companies investing a lot of resources in the language: Cloudflare, Coursera, Discord, Dropbox, Figma… I strongly recommend them, as most have happy endings but also a couple of non-trivial problems on the road.

Unsurprisingly, it flooded my RSS feed for the last couple of years. Whether it’s yet another “X in Rust” post, a successful company story (like the ones above), or a new programming language – they are a significant part of my readings.

Closing thoughts

Rust is here to stay – that’s for sure. I’d say we’ll see more and more tools going the same path as Parcel, that is, some of their parts will be incrementally rewritten in Rust (or other languages). I believe we all will benefit from that, especially on the performance and stability side.

How much of the webdev tooling will be in Rust a year from now? We’ll see.

5

I just had to jokingly mention “Rewrite everything in Rust”. Jokes aside, there is an actual list of projects rewritten in Rust. If you are a frequent terminal user, you may find something for yourself. That’s how I found hyperfine!

1

I assume that you’re using rustup – an official tool for managing your Rust installation (like nvm for Node). It comes with a few components by default.

2

I don’t consider myself a rustacean, but not a bad Rust programmer either. And yet, a few notes from cargo clippy improved the performance of my code by 15%! To be exact, it suggested some non-trivial changes in the internal representation of the data that allowed me not to clone it that often.

3

It reminds me of the dual nature of methods in Nim. Basically every function that accepts an argument of type T is its method. There are two method call syntaxes: f(x, a, b, ...) or x.f(a, b, ...) (with some limitations).

4

There is, however, a significant group of people against it. For example, swc that is essentially a Babel and Terser combo in Rust, is definitely faster. But the fact that it’s not written in JavaScript makes it significantly harder for its users to contribute or even report bugs. It’s not a Rust-specific thing – esbuild is a similar case but in Go.