We put the first Rust service into production a little over two years ago. It was a small thing, a request-shaping proxy that sat in front of an API and rewrote a few headers, and it was small precisely because none of us wanted to bet the quarter on a language we mostly knew from weekend tinkering. That service is still running. It has not paged anyone since the second week. I want to write down what actually happened, the wins and the regrets, before nostalgia sands the edges off.
The biggest win is the one everyone promises and I half-believed: the class of bug that used to fill our incident channel just stopped arriving. No segfaults, no use-after-free, no surprise nil deref at 3am from a code path nobody exercised in testing. When the compiler is happy, a whole category of "how is this even possible" outages doesn't happen. That is not marketing. That is a measurable drop in a specific kind of ticket, and after two years the trend held.
The second win is performance we didn't have to fight for. We rewrote a Python batch job that chewed through log lines, and it went from "kick it off and go to lunch" to "done before the terminal redrew". Some of that is just leaving an interpreted language behind, granted. But the memory profile was the real prize: flat, predictable, no GC pauses showing up as latency spikes on the p99 graph. For a thing that has to keep up with a firehose, predictable beats fast.
Now the regrets, because there are some.
Compile times are the tax you pay every single day. On a cold build the proxy takes long enough that I context-switch, and context-switching is where I lose the thread. We threw sccache at it and split crates more aggressively, which helped, but I won't pretend it's a Go-like edit-build-run loop. It isn't.
The bigger regret is async. We reached for tokio early because everything in the ecosystem assumes it, and for the proxy that was correct. But we also dragged async into a couple of services that were perfectly happy being boring and synchronous, and the result was a function-colouring mess and error types that needed a small treatise to read. If I had the two years back, I'd write more plain blocking Rust and only go async where the workload genuinely demanded it.
The last regret is a people one. Rust is a real cliff to climb, and we underestimated how long the second and third engineers would feel slow before they felt fast. We should have paired more deliberately in month one instead of letting people fight the borrow checker alone and quietly conclude they were stupid. They weren't. It just takes a while for the rules to stop feeling like punishment and start feeling like a handrail.
Would I do it again? For the services where correctness and steady latency actually matter, without hesitation. For a CRUD endpoint that talks to a database and returns JSON, I'd think hard, and I'd probably still reach for something I can hire for and ship by Friday. The trick, two years in, is that Rust is not a religion. It's a sharp tool that earns its keep in some places and sulks in others, and the win is knowing which is which before you commit.