Skip to content
fr0staman
All posts

Rewriting a hobby bot in Rust, two years in

3 min read

In September 2021 I started a Telegram bot in Python, with aiogram, for no better reason than that it seemed funny. In August 2023 I rewrote it in Rust. It still runs, it has grown to roughly 20,000 daily active users, and the process sits at about 1% CPU.

"We rewrote it in Rust" is close to a genre at this point, so let me be specific about what actually happened, including the parts that argue against doing it.

The Python version was not the problem

It is tempting to justify a rewrite with the language you left. That would be dishonest here. The Python bot worked. It handled the load it had. Most of what people attribute to a language rewrite is really attributable to writing the same system a second time, with the requirements finally understood — and that would have improved the Python version too.

What was true is narrower: the bot had grown a set of behaviours I did not fully trust myself to change. Inline queries, a per-chat game, a duel system where two users' state changes at once. Nothing exotic, but enough moving parts that a careless edit could corrupt somebody's progress, and I had no type system telling me when I had missed a case.

What Rust actually gave me

Exhaustive matching, mostly. The compiler refusing to build until every variant is handled turned out to be the feature I use hardest. Adding a new inline action is now a conversation with the compiler about the places I forgot, rather than a hunt for the ones I broke.

A resource profile I stopped thinking about. This is the part that reads as a Rust advertisement, so I want to put a number on it honestly: 20,000 daily users, roughly 1% CPU. That figure is measured, not estimated — the bot exports Prometheus metrics, which was itself a habit I picked up from paid work and brought back here. The practical effect is not speed; it is that the bot shares a server with other things and never becomes their problem.

Confidence to leave it alone. A side project's real enemy is not performance, it is the moment you stop wanting to open it. Compiling before it runs means I can come back after three months away and change something without re-reading everything first.

What it cost

Months, spread across evenings. And a specific kind of tedium: the Python version took a weekend to reach feature parity with itself in the first place, and the Rust version took considerably longer to reach parity with the Python one. Every small convenience — sending a photo, parsing a command, storing a row — had to be found again in a less forgiving ecosystem.

I would not have done it for a client on those terms. The calculation is different when the thing being spent is your own evenings and the thing being bought is your own future willingness to maintain it.

The part that transferred

The unexpected return was in the day job. Working in Rust changed how I read code in every other language — particularly what I now notice about the states a piece of code can be in and never handles. That habit came back with me into TypeScript and, oddly enough, into twenty-year-old Perl, where the interesting question is almost always "what happens in the case nobody wrote down".

A side project justifies itself if it teaches you something you take to work. By that standard the rewrite paid for itself before it shipped.

Share