Performance: Rust and its relationship with Node.js

Performance: Rust and its relationship with Node.js

Alberto Esposito
Alberto Esposito 7 Min Read

Hello everyone, and welcome to the Sprkl Expert Talk. This time, we’ll discuss Rust and its relationship with Node.js regarding performance. In our expert talks, we host a prominent developer in each episode and explore topics that would bring value to the community. 

I’m Raz, a software engineer at Sprkl Personal Observability, and I’m the person who came up with those questions. 

Sprkl is a Personal Observability platform that provides personalized feedback on your code changes while coding in the IDE. Sprkl helps ship correct and efficient code while spending less time on debugging, code review, and frustrating rework. Powered by OpenTelemetry, Sprkl instruments every code change and analyzes it upon execution. Check out Sprkl on VS Code marketplace

This time we interviewed Alberto Esposito, software engineer, consultant, and Node.js enthusiast. The interview with Alberto was very insightful for me, and I hope you’ll also gain some value from it 🙂

Key Takeaways  

  • Rust is becoming very popular because you have implicit proof that your code is reasonably safe and readable when using Rust. The recent introduction of the Linux kernel proves that Rust is tackling some deep pain points in the lives of engineers. 
  • It’s interesting to note that although SWC has fewer stars than its Golang counterpart Esbuild, it has double the number of contributors and much more activity in the codebase.
  • Rust and Node.js are a match made in heaven, thanks to WASM. It represents a natural development path for backend and full-stack engineers.
  • Developers should avoid NAPI as much as possible and prefer WASM. Portability is one of the reasons.
  • When integrating Node.js and Rust,  it’s essential to strike the right balance, starting with a pure Node.js implementation and then migrating hot paths or critical code to WASM+Rust. 
  • TS developers earn almost 30% less than Rust developers.

Please tell us about yourself:

My name is Alberto, I’m a 34-year-old engineer from Italy. My background is in applied math, focusing on computational statistics and numerical simulations. I’ve worked in the tech sector for almost 15 years, starting as a consultant right out of high school and then moving across all of Europe.

I recently decided to take a sabbatical year to write a next-generation database, yottastore, and hopefully turn it into an open-source startup. I also work a lot on io_uring, one of the most critical emerging technologies in the Linux space, a revolution for backend developers. 

A fundamental programming paradigm is that code is easier to write than to read. Think, for example, about the keyword `auto` in C++: it’s very easy to type, but as a code reviewer, it will require me to analyze each call’s context to understand its meaning. Similar examples can be made for Javascript or loosely typed Typescript.

With Rust, instead, I will probably spend more time writing code that will satisfy the borrow checker, but in exchange, I will have implicit proof that my code is reasonably safe and readable.  

As a project maintainer, it’s easier to review and accept a pull request made in Rust; as a newcomer, it’s easier to understand what the code is doing thus enabling faster onboarding. 

The recent introduction of the Linux kernel proves that Rust is tackling some deep pain points in the lives of engineers, helping us to be more productive and write correct code.

And what about the Node.js community? Why is the Node.js community becoming very aware of it: Projects like SWC, Prisma, and Next.js all incorporate Rust code.

It’s interesting to note that although SWC has fewer stars than its Golang counterpart Esbuild, it has double the number of contributors and much more activity in the codebase.

I think it’s a fair picture of Rust and its ecosystem: smaller than others at the moment but very active with a more significant amount of collaboration, enabled by the guarantees made by the language.

Coming from a Node.js developer perspective, how to leverage Rust for performance and safety? How would you suggest approaching it?

Rust and Node.js are a match made in heaven, thanks to WASM. It represents a natural development path for backend and full-stack engineers who want to write better code without wasting years of experience in the Node.js ecosystem.

Let’s take an example: for speed and efficiency reasons, I would like to switch my app from JSON to CBOR. I could write a native Javascript implementation, but it would be extremely slow compared to the highly optimized JSON encoder/decoder written in C++ for V8. CBOR is also tricky because different encoders might not be compatible (see here).

A much better approach could be to write a `no_std` implementation that could then run in browsers and backend runtimes thanks to WASM.

NAPI (native bindings) vs. WebAssembly? How do they compare, and what are the use cases suiting each?

I personally think that developers should avoid NAPI as much as possible and prefer WASM. Portability is one of the reasons, but the build system’s complexity and some unfortunate design choices drove me away, an opinion shared by Ryan Dahl, who decided to follow a different approach with its rewrite of Node.js in Rust, Deno.

But WASM has its limits: it’s a sandboxed API, so it cannot access the system. To do that you need a NAPI bridge. 

For example, we can take the implementation of HTTP/3 for Nodejs, which was started more than 4 years ago by heavyweights like James Snell and Matteo Collina; after multiple scope cuts, the issue is still pending due to complexities in the NAPI or security bugs. It’s clear to me how Rust could have been of help here.

A different approach I took to implement QUIC in node.js was to bridge an equivalent to the Syscall module via NAPI to do the heavy lifting in Rust using `io_uring`. This way, you have a portable, self-contained WASM module, with strong safety guarantees thanks to Rust, in an architecture more similar to micro kernels than the monolith Node.js has become.

Let’s wrap up with the pitfalls of Rust and Node.js integration. What should you be aware of when writing such a library, and when should we be better off just writing a complete Rust web server instead?


Things like:

  • The integration overhead
  • Error handling
  • Logging (matching formats)
  • JS/TS wrapping files for better interface (auto-completion, JSDoc, etc.)
  • Any other ideas?

As a manager, I want to use Typescript as much as possible: it’s easier to hire developers who use TS, and their salaries are almost 30% less than Rust developers. Infrastructure is much cheaper than engineers, and most of the time, I’m pressed for fast deliveries rather than safe code.

As an engineer, I want to use Rust: it’s better for my career and makes my future life easier by allowing me to trust the existing codebase.

I think it’s essential to strike the right balance, starting with a pure Node.js implementation and then starting to migrate hot paths or critical code to WASM+Rust. Writing pure Rust should be reserved for mission-critical services, where performance is paramount, like a proxy server or a database.


For more insights, follow us on Twitter, or LinkedIn or ping me if you have any thoughts about our discussion.

 

 

 

Share

Share on facebook
Share on twitter
Share on linkedin

Enjoy your reading 7 Min Read

Further Reading