https://yieldcode.blog/post/farewell-rust/
yield code(); yc();
Home About LinkedIn Hire Me Snippets Talks Books Newsletter RSS
Home About LinkedIn Hire Me Snippets Talks Books Newsletter RSS
Programming Entrepreneurship Rust Nodejs Typescript
Feb 19, 2026 · 14 min read
Farewell, Rust
For now...
Prelude
When I was in 9th grade -- last year before high-school -- my best
friend persuaded me to join, together with him, the schools
programming club. At first, I hesitated, but later agreed. I am
immensely thankful to him for this.
There, step by step, we learned Pascal using Turbo Pascal. Little by
little, we grasped the basics of the language: variables, operators,
string manipulations, data structures -- until eventually, remaking
Conway's Game of Life. And then, summer break came. Other than HTML,
which is not a programming language, Pascal was the first real
programming language I have learned and used.
But I did not stay with Pascal for very long time. After the summer
break, in high-school, I have chosen the "Software Engineering"
branch of studying, and there we learned C. Just like Pascal, we
started step by step: basic operators, string manipulations, memory
allocation, data structures, and the final boss, the void *.
I fell in love with C. The precise control over memory; passing
variables by reference or pointer; the need to allocate memory for
every data structure that you wanted to create.
After 3 more years of studying, I have graduated high-school. In that
time, I have improved my C skills, learned some PHP, picked up C++,
and tried to build a variety of programs (don't judge me too hard,
these are ~20 years old): a clone of BattleCity, 3D software renderer
, IRC bot, unfinished operating system kernel, unfinished game engine
, TTF renderer for OpenGL.
After high-school, I have enrolled in a two-year college program that
would earn me a Practical Software Engineering Degree. During the
summer break between the first and the seconds years, I had a choice
to make: go work in McDonald's (I had experience working as a waiter
during high school summer breaks), or find a job in software
engineering. After 2 seconds of hesitation, I have crafted a CV and
started to send it to every position I saw online. And despite the
fact that I really wanted to get a software development position in C
or C++, nobody would hire me. Eventually, I have secured a
web-development position in PHP (thank you very much my first
employer for giving me a chance).
And this would be the last time I'd touch C or C++. Dynamic,
high-level languages such as PHP, Python, and Ruby -- are more suited
to the dynamic nature of web development. You rarely need to squeeze
the maximum performance from your hardware, since for every second
you gain by optimizing data structures allocations in C, you lose 10x
more waiting for network or disk requests to resolve. And so,
collectively, we all agreed that the web is better to be written in
dynamic languages.
But just like your first true-love, C would hunt me. I would obsess
about micro-optimizations, and would get mad that I can't control
when variables are allocated, and how to pass things by reference or
pointer without making a redundant copy. And then, Rust became a
thing.
Web development in Rust
Just like every software engineer, I started learning Rust by
building my own game engine. Do you even call yourself a software
engineer if you never tried to build your own game engine? And I fell
in-love with Rust. C was aging, and failed to catch up with the
moving world of hipster development: modern tooling, linters,
formatters, package management. Rust offered the best of both worlds:
low-level control of memory allocation, and variable life-cycle,
while having modern development practices: a simple-to-use compiler,
built-in linter and formatter, and of course a modern package
management tool and repository.
The community was loving Rust, and the language was growing very
fast. And for the first time you could write a web application using
a low-level systems-programming language. ^1 After picking up the
basics of the language, and abandoning my game engine, I have decided
to build a web application in Rust. And, in the end of 2023, I have
succeeded. I have built, and shipped a fully operational web
application that was making me money, in Rust.
Throughout the years, I learned from my mistakes, and shared my
progress here, here, and here. Eventually, the application became a
Frankensteins' monster were the backend would be pure Rust, while the
frontend was a statically generated Astro website. Until, in the end,
I have reached a road-block and could no longer maintain or extend
the application.
I took a painful decision to migrate everything to Node.js, and
conclude this experiment with Rust. It was hard for me to make this
decision. I really loved working with Rust, and I wanted to make it
work. My work with Rust earned me valuable human connections, and
opportunities to present my knowledge at two conferences, and one
meetup. I am happy that I have took the risk with Rust, but...
Farewell Rust, at least for now...
I'm not going to go over the positive sides of Rust, since I covered
them in other articles such as Building a web app in Rust and One
year of Rust in production. But I do want to talk about some of the
struggles I had, both as a reminder to my future self (you can take C
from me, but you can't take my love to low-level programming), and as
a cautionary tale to everyone else.
Keep in mind that everything I share is based on my perspective: I'm
a solo founder, building and running a web application. There is no
heavy workload like video processing, nor a need to have low latency
-- two things at which Rust excels. From my experience, and my
product, the bottleneck is always the database, disk, or network. I'm
also, somewhat, experienced in Rust, so I'm not the "I tried to build
a web app in Rust, but I don't understand borrow checker, so Rust bad
" kind of guy. Now, with that out of the way, let's begin.
Templating
As I mentioned earlier, from pure Rust + server side generated
templates with tera, I have switched to a Rust API server + Astro
static website that calls the API. The reason I moved away from
rendering HTML in Rust, is the fact that I am too spoiled with
type-safe templates. With Astro, I can use the .astro components that
are type-safe. Since I use, and advocate for, Typescript, all my
templates are type-safe, and there is very little chance to mistype a
variable, or miss it. If you have a component like this, it's very
hard to make it compile with the wrong type (unless you do stupid
stuff like props as any):
interface Props {
username: string;
email: string;
}
export function UserCard(props: Props) {
return (
Hello {props.username} ({props.email})
);
}
With libraries like tera, handlebars, or mrml -- your view is
essentially separated from the props, and if you rename a field in
the template, you must remember to rename it in the model as well.
It's possible to solve it with tests at the cost of longer CI/CD
pipeline, but I will talk about compilation time a bit later.
Functions inside templates are basically wild-west of strings. And
you need functions. It's hard to make rich templates without
functions.
But there are libraries like maud or askama -- I hear you say. And I
agree, there are. These are essentially type-safe HTML templates that
use the macro mechanism from Rust. By building, basically, HTML DSL
using Rust macro, you can create compile time, type-safe templates.
But then again, compilation is expensive, but more on that later.
Localization and Internationalization
I have been talking about localization back in 2015. Node.js ships
with full icu support, and a set of Intl.* APIs to format numbers,
lists, currencies, country names, you name it. On top of that, with
libraries like i18next, one can get type-safe auto-complete for
translations, something that I was not able to achieve in Rust. Yes,
Rust has support for fluent translation files developed by Mozilla,
and a minimal support for number formatting. But Node.js has
everything you need in order to build and ship fully localized and
translated web-applications. It's a know fact that i18n is lacking in
Rust (see: AWWY: Internationalization), and bindings for icu4c are in
progress, but they are nowhere near what Node.js can offer it that
regard.
The web is dynamic by nature
Take it or leave it, but the web is dynamic by nature. Most of the
work is serializing and deserializing data between different systems,
be it a database, Redis, external APIs, or template engines. Rust has
one of the best (de)serialization libraries in my opinion: serde. And
yet, due to the nature of safety in Rust, I'd find myself writing
boilerplate code just to avoid calling .unwrap(). I'd get long chain
calls of .ok_or followed by .map_err. I defined a dozen of custom
error enums, some taking other enums, because you want to be able to
handle errors properly, and your functions can't just return any
error.
Similar thing can be said about writing SQL. I was really happy with
using sqlx, which is a crate for compile-time checked SQL queries. By
relying on macros in Rust, sqlx would execute the query against a
real database instance in order to make sure that your query is
valid, and the mappings are correct. However, writing dynamic queries
with sqlx is a PITA, as you can't build a dynamic string and make
sure it's checked during compilation, so you have to resort to using
non-checked SQL queries. And honestly, with kysely in Node.js, I can
get a similar result, without the need to have a connection to the
DB, while having ergonomic query builder to build dynamic queries,
without the overhead of compilation time.
Okay, I hear you, let's talk about compilation time.
Compilation time
Rust achieves its safety at the expense of, somewhat long,
compilation time. On modern hardware, the compilation time is not
that bad. It's not good enough to have uninterrupted
change-f5-preview cycle, but it's good enough especially with
incremental compilation.
However, the more crates you have, the more macros you use, the
slower the compilation time becomes. So the problem becomes is how to
achieve fast compilation during CI/CD. Due to the dynamic nature of
the web, you often times find yourself in a loop of: there is an
error in production - fix it - deploy. And you want this loop to be
as fast as possible, because you have customers who can't do things
with your app.
I have my own hardware to run CI/CD workers. A dedicated VM on an
Intel Core i5-7500 with 32GB of RAM. The VM has access to all 4 CPU
cores, and would take about 14 minutes from push to master until the
docker container was deployed on the server (about 12 of it is the
docker stage with compilation). That's with multistaged docker file,
and a cached builder layer; with no cache, it would probably take
~20-25 minutes. And it does not include running tests or clippy as
part of the CI/CD. I simply gave-up trying to set-up proper caching
so tests, clippy, and compilation would be able to use the same
cache.
Node.js, on the other hand, takes on average 5 minutes, including
linting, and tests. And since I moved to Node.js, I added another
back-office service, so I deploy more code, 3 times faster, while
actually relying on running tests and lint as part of the CI/CD
pipeline.
Ecosystem maturity
Rust has a very mature ecosystem in most aspects, but it is lacking
in the web aspect. Need an obscure third party API? It's probably not
there. So you end up implementing APIs instead of doing core business
logic. Each API is additional code that you need to test and
maintain.
It's not hard to implement REST APIs, but the lack of maturity does
slow you down. I had to implement, and test, third party APIs, queue
mechanism on top of PostgreSQL, code to validate webhook signatures.
It was fun, but it comes out of the box in other, "standard",
languages.
Node.js is good enough
And honestly, Node.js is good enough. People like to criticize
Node.js or the npm ecosystem, implying that there is something
fundamentally wrong with JavaScript. And sure, JavaScript is far from
being a good language. There are many quirks in the language, which
you can learn only with experience, pain, and tears. And I miss stuff
from Rust like the Result and Option types; I miss the match
statement; and I miss the enum as well. But in all honesty, Node.js
ecosystem has matured and is stable to write web applications. You
have libraries like zod to validate request/response JSONs, libraries
like kysely, @kitajs/html, and my own @mjmx/core to write type-safe
SQL, HTML, and MJML. And async/await is still better in Node.js than
in Rust. It's always weird to need to bring thirds party async-trait
crate because you want to have an async method in a trait.
There are still issues with Node.js. The never-ending deprecation of
cjs in favor of esm; it's mostly good, until you encounter a cjs
package, and then it's hell. The cumbersome eslint and prettier setup
where you need to juggle between 2 files and dozen of plugins to get
basic functionality you get with clippy out-of-the-box; I wait for
the day biome will close the gaps, and becomes the standard tool.
Workspaces are still not solved, although pnpm is better in this
regard, but it's nowhere near the workspaces in cargo. And the
occasional struggles with typescript where the runtime seems to be
changing too often; is it ts-node? tsx? tsm? The built-in typescript
runtime in node? deno? bun?
Oh, and I really wish we all would just agree that snake_case is
simply the best, most aesthetically pleasing, and easy to read case
for programming. I really hate the fact that JavaScript has adopted
the camelCase.
Closing notes
Building solo is hard. You wear 10 hats as software engineer, in
addition to the dozen hats as an entrepreneur. I really wanted Rust
to succeed, but I also need to move forward. I found myself ignoring
bugs in Sentry because it meant going back to long compile times. I
was postponing feature development because it meant slow iteration
speed, and trying to synchronize backend REST API with frontend, or
the need to visually re-test every page after a variable rename in a
template. Call me spoiled, but I don't have the luxury of development
team, and someone checking my code.
Rust excels at non-visual things, i.e. things you can write and wrap
with tests. But when you need to develop UI, it becomes painful. You
wait for the code the recompile; worried about passing the wrong
variable to a view.
From pure Rust, I had to go to Rust with a statically generated HTML
+ Alpine on top of it. To make sure my email templates are safe, I
was considering writing a dedicated mailing service in NodeJS, just
so I could use react.email (or my own @mjmx/core), and get some
type-safety. It's as if I was moving backwards, breaking my Rust
monolith and rewriting parts in a dynamic language that is better
suited for the web.
No, Node.js is not perfect. But at least I have one stack, and funny
enough, I get more type-safety than I had in Rust. Sure, it's a fake
type-safety, JavaScript is a dynamic language, but I no longer have
to jump between my view files and my model and try to keep them in
sync. I no longer misspell translation keys, resulting in blank
words; emails do not come with Hello {{dearCustomer}}.
It seems like most of my issues boil down to dynamic things:
templates, i18n, SQL. If I were to write an API service today, I
would probably choose Rust again, since API services do not have to
deal with views or translations. I still don't know how I would
handle SQL though. ORMs are not my cup of tea, and other than sqlx,
Rust seems to lack a good type-safe query builder.
I also miss the small footprint of the application. With Rust, the
containers would use between 60 and 80 MB of RAM; but with Node.js,
the lowest I have is 117 MB of RAM for the back-office, without any
load. And it's true what they say: use the right tool for the job.
Rust shines in CPU-heavy tasks, and for sure I will be using it when
I will have such tasks.
But until then, farewell Rust.
Footnotes
1. Yes, there is Go. But I never really had the chance to like Go. -
Share this:
Published by
Dmitry Kudryavtsev
Dmitry Kudryavtsev
Engineering Leadership, Senior Software Engineer / Tech Entrepreneur
With more than 15 years of professional experience in tech, Dmitry is
a generalist software engineer with a strong passion to writing code
and writing about code.
Mastodon Verification
---------------------------------------------------------------------
Technical Writing for Software Engineers - Book Cover
Recently, I released a new book called Technical Writing for Software
Engineers - A Handbook. It's a short handbook about how to improve
your technical writing.
The book contains my experience and mistakes I made, together with
examples of different technical documents you will have to write
during your career. If you believe it might help you, consider
purchasing it to support my work and this blog.
Get it on Gumroad or Leanpub
---------------------------------------------------------------------
From Applicant to Employee - Book Cover
Were you affected by the recent lay-offs in tech? Are you looking for
a new workplace? Do you want to get into tech?
Consider getting my and my wife's recent book From Applicant to
Employee - Your blueprint for landing a job in tech. It contains our
combined knowledge on the interviewing process in small, and big tech
companies. Together with tips and tricks on how to prepare for your
interview, befriend your recruiter, and find a good match between you
and potential employer.
Get it on Gumroad or LeanPub
Previous Post
Introducing mjmx: a custom JSX runtime for mjml
You might also like...
Mar
10
Serving Astro with Rust
10 min read
Dec
11
NodeJS Native Module vs WASM
13 min read
Oct
23
Supercharge Your NodeJS With Rust
15 min read
Feb
6
Introducing mjmx: a custom JSX runtime for mjml
2 min read
Sep
22
One year of Rust in production
10 min read
2026 (c) Dmitry Kudryavtsev Unless otherwise noted, all blog posts are
published under CC BY-NC 4.0 license.
ef4c92dd