# Rust is my infrastructure language and I'm done apologizing for it

> The stock objections to Rust in infrastructure, answered from three systems I actually shipped: a rate limiter, an eBPF agent on a robot fleet, and the gRPC gateway I run today. Including the places I still reach for Go and Python.

Author: Karan Vijayakumar
Published: 2025-05-17
Tags: rust, infrastructure, sre, opinion
Reading time: 8 min
Canonical URL: https://karanvk.me/blog/rust-infrastructure-language

---

I caught myself pre-apologizing in a design review last month and it annoyed me for the rest of the week.

Small internal service, the kind that lives on the request path and does one job. I'd written "in Rust" on the slide and then, unprompted, added a paragraph of hedging underneath. Compile times are manageable now. It's not that hard to hire for. We can always rewrite it. Nobody had asked. I'd built the defense before anyone attacked, the way you do when you've had an argument enough times that you start having it with yourself.

So this is the post where I stop. Not "Rust for everything," a position held mostly by people who haven't maintained enough software. Rust for infrastructure, specifically, because that's the one place where the trade Rust asks you to make is obviously worth it.

## The three systems I'm arguing from

Abstract Rust arguments are how we all got tired of this topic, so: three systems, all shipped, all with real consequences.

At FourKites in 2022 and 2023 I built a distributed rate limiter, gRPC, roughly 100,000 requests per second, in front of services that would fall over if you let too much through. Then an L4 router with automated failover: a program whose entire job is being correct about state transitions under stress.

Then Rapyuta Robotics in 2024, an eBPF agent on Aya running across a fleet of warehouse robots. Kernel scheduling, disk, and socket metrics, aggregated on the device, shipped as tiny payloads, inside a CPU budget of one percent, because the other 99 belonged to control loops that could not miss a deadline.

Now, at Sales Marker, I own a Rust and Tokio gRPC gateway doing 100,000 plus requests per second with sub-millisecond added p99 overhead, fronting a couple of thousand vCPUs. It's the software I know best and the reason I've stopped hedging.

## But the compile times

They're bad. I'm not going to pretend otherwise, because the pretending is what makes Rust advocates insufferable.

Honest numbers from the gateway: a clean release build in CI takes a bit over four minutes. An incremental `cargo check` on the crate I edit most is three seconds, an incremental debug build about eleven. The Go team down the hall has a pipeline that finishes before mine has finished linking, and when we compare notes they win every time.

What I do argue is that the comparison is drawn in the wrong place. Four minutes of CI, twenty times a day, is eighty minutes of machine time and roughly zero minutes of my attention, because I'm reading a diff while it runs. What it buys is a class of bug that never reaches the estate. I have not debugged a data race in that gateway. Not once, in eighteen months, at six figures per second across a couple of thousand cores. I have lost whole weeks to data races in other people's C++ and Python, weeks where the bug reproduced one time in four hundred and the fix was a print statement that changed the timing.

The practical mitigations are real and badly underused. Split the workspace so the crate you edit isn't the crate everything depends on. Use `cargo check` in the editor loop. Switch the linker to `mold`, twenty minutes of work that cut link time by more than half. Cache with `sccache` in CI. None of that makes Rust compile like Go, but it moves the pain from every keystroke to a few times a day, which is the difference between a tax and a grievance.

## But you can't hire for it

We put two engineers onto Rust services last year, neither of whom had written Rust professionally. Both were shipping useful pull requests inside a month. Not because I'm a gifted mentor, but because the work on an infrastructure codebase is lopsided: most changes add a metric, a config knob, a route, a validation rule, and none of that requires understanding pinning or variance.

The genuinely hard part isn't Rust, it's async Rust, and specifically lifetimes on a hot path where you're trying not to allocate. That's maybe five percent of the code and it's the five percent I review carefully. Draw the boundary honestly and the onboarding problem gets much smaller than the meme suggests.

The other thing nobody mentions: people want to write Rust. Every role I've posted drew candidates who were excited rather than resigned.

## But the borrow checker fights you

The borrow checker fights the first draft of a design you hadn't finished thinking about. That isn't the same thing, though it feels identical at the time, usually around 6pm.

The robot agent is my evidence. Three concurrent things: a loop draining kernel maps on an interval, an aggregator folding samples into histograms, and an uplink shipper handling a network that was down more often than up. Shared mutable state between all three, on devices I could not SSH into when they misbehaved, with a hard requirement never to stall. In C that's a design I'd have gotten wrong and then gotten wrong again in the fix. The Rust version deadlocked zero times in production, and that's not a claim about my ability. It's a claim about a compiler that refused several designs I was pleased with until I fixed the ownership story, at which point the deadlock was no longer expressible.

Most infrastructure is control loops wearing marketing: a router that must fail over correctly, a leader election, a drain that has to finish before the pod dies. Those failure modes are silent and intermittent, which is exactly where a compiler pedantic about aliasing is the cheapest design review available.

## Why infrastructure is where the bill comes out in your favor

Tail latency is the product. Not a feature of it, the product. The gateway's p999 is the company's p999 because everything goes through it, and a garbage collector, however good, is a scheduled outage of unpredictable size inserted into that number by something other than me. Go's collector is excellent and sub-millisecond pauses are the normal case, and "normal case" means nothing at p999. There is nothing there to think about, which is a strange kind of luxury.

A static binary is an operations story, not a build detail. The robot agent shipped as one file, linked against musl, onto devices with four gigabytes of storage and no Python. Nothing to version, nothing to keep in the image. That's what made the project feasible, and it's the same property that makes Rust pleasant in a container: small image, fast start, and a memory footprint you can predict from first principles instead of from a graph. Across a couple of thousand vCPUs, the difference between a component that needs 40 MB and one that needs 400 MB is a line item, and my one conversation about it with a finance team was very short.

Rust's costs are paid at compile time and its benefits are collected at 3am.

## Where I don't reach for it

Throwaway scripts stay Python or bash and I don't feel a flicker of guilt. The forty line thing that reads a CSV and files tickets, the migration that runs once, the glue nobody will open again. Rust's advantages are about what happens to a program over years of operation, and a program with no years ahead collects none of them while paying full price.

The bigger concession is Kubernetes controllers. `kube-rs` is good, genuinely, and I've written a small controller with it and enjoyed it. But client-go's gravity is real and pretending otherwise costs weeks. Informers, generated clients, controller-runtime scaffolding, and the fact that every example on the internet assumes Go. When I need a controller this quarter I write Go, it's the right call, and my preferring Rust is not an engineering input.

The tell is whether the thing sits on a hot path or a control path with real consequences. Gateway, rate limiter, router, on-device agent: Rust. Operator reconciling a CRD every thirty seconds: Go. Script: Python. That heuristic has never embarrassed me.

## The bill I actually pay

It isn't compile times and it isn't hiring. It's that the second person on a Rust service costs more than the second person on a Go service, and I mitigate that rather than deny it.

What helps: keeping the async and unsafe surface small and physically located in one module, so most contributors never open it. Writing the house rules down as a short document rather than a wiki nobody reads (bounded channels always, `spawn_blocking` for anything not provably microseconds, no `std::sync::Mutex` across an await). Encoding the dangerous ones as `disallowed-methods` in `clippy.toml` so CI enforces them and I'm not the enforcement mechanism. Newtypes at the edges so business logic deals in `TenantId` and never `&'a str`. And pairing on somebody's first pull request, worth more than all the documentation put together.

Then boring architecture on purpose. Owned tasks talking over channels rather than shared state behind locks, because it's the shape that's easiest to reason about, test, and explain to someone in their second week. Most of the Rust that scares people is Rust somebody wrote to be clever.

That's the whole argument, and it's narrow on purpose. Rust is my infrastructure language because infrastructure is where a program's lifetime is long, its failure modes are silent, its tail latency is somebody's SLO, and its deployment story is an operational fact rather than a build artifact. In that corner the trade isn't close.

I'll keep writing Python for the CSV thing. I'm just not opening with an apology anymore.
