← Writing · No 07 · Stack

Rust as the boring choice for solo tooling

By Felix Hellström · Stockholm · 1180 words

Rust as the boring choice for solo tooling

Three of the four CLIs and servers I run every day are now Rust. The fourth being JavaScript is my fault, not its fault.

The reason I keep reaching for Rust isn't a language preference. It's that I work with a fleet of AI agents as coworkers, and the stack choices that matter most are the ones that shorten the agent's iteration loop. Rust shortens that loop more than Python or Node, and the difference is bigger than I expected when I started this transition.

This is not a Rust evangelism post. I'm an intermediate Rust user at best. I will not defend the borrow checker against people who don't enjoy it. But over the last year, Rust has become the default for everything I write that's a CLI, an MCP server, or a long-lived background process. I want to explain why, in case you're a solo developer with a Python or Node default and you're wondering if it's worth switching.

Python is fine, but

I wrote a lot of Python CLIs between 2020 and 2024. They worked. They still work. The reason I stopped reaching for Python is not that anything broke; it's that several small frictions added up.

Distribution. Python CLIs ship as packages on PyPI, and the install path is "the user has Python, they know pip or pipx, they can manage their environment." For a CLI I want my friends or clients to use, those preconditions fail more than half the time. macOS ships with system Python that's deliberately fragile to install into. People run pip install and it fails on a missing C compiler. They want me to support Python 3.10 because that's what their corp Mac has.

Startup time. Cold-start a Python CLI and you're paying ~150 ms before any of your code runs. For interactive tools that's noticeable. For tools an agent calls in a loop, it's a tax on every iteration.

Tooling sprawl. Each CLI ends up vendored with its own requirements.txt, its own preferred Python version, its own opinions about virtualenvs. Maintaining ten of them is not maintaining one ten times.

The friction is small per CLI. It compounds.

Single binary changes the install ergonomics

The shape Rust gives you is: one binary, no dependencies, copy it to /usr/local/bin and forget about it.

That sounds trivial when you say it out loud. It is not trivial when you've been distributing Python CLIs for five years.

A cargo install from a published crate produces a binary in ~/.cargo/bin/ that runs anywhere on the same OS without further setup. A cargo build --release produces the same binary you can hand to a friend over Slack. There is no pyenv, no pipx, no virtualenv, no missing system library. The binary contains everything it needs.

For an audience that includes me-on-a-fresh-laptop, friends who don't write code, and clients who run macOS, the single-binary distribution is genuinely better.

Type safety as an agent's advantage

This is the load-bearing reason, the one I underweighted before I tried it.

I write most code with a fleet of Claude agents now. The thing I noticed switching from Python to Rust isn't the speed of the runtime. It's the speed of the agent loop.

When I ask an agent to add a feature to a Python CLI, the iteration looks like: agent edits, I run the tool, it crashes on a typo or a wrong-shape dictionary, I paste the traceback back, agent fixes, repeat. Maybe three rounds.

When I ask an agent to add a feature to a Rust CLI, the iteration looks like: agent edits, cargo check runs, the compiler finds the typo or the wrong-shape struct, the agent reads the error, fixes, and only then do I run the tool. Maybe one round.

The difference is the type system catches the class of errors that Python and Node would have caught at runtime. The agent doesn't need me as the runtime; it has the compiler.

For agent-driven development specifically, this matters more than it does for human-driven development. A human can hold the shape of a small program in their head. An agent reasoning about a 6,000-LOC Rust crate it has only partial context on benefits enormously from a compiler that says "no, that field is Option<String>, not String."

The compounding effect is the part to internalize. Each agent iteration that passes through the compiler instead of through me-as-runtime saves a round-trip. A round-trip is maybe two minutes of human attention plus the cost of context switching. Across a workday with twenty dispatches per agent, that's the difference between agent-as-coworker and agent-as-burden.

I did not predict this when I started writing more Rust. The "agent likes types" effect surprised me, and it's now the primary reason this article exists.

The tools I've shipped in Rust

Concrete receipts so this isn't pure aesthetics.

clone-a-website: a CLI that takes a Webflow URL and produces a deployable static replica with rewritten asset paths. About 2,000 lines. I built it because I needed to migrate this site off Webflow, and Cloudflare Pages billed less for a static site than Webflow billed for hosting. It has cloned six sites for me without manual cleanup.

fragments: a 200-line CLI that does literal text substitution between marker comments in HTML files. Used to manage shared <head>, nav, and footer blocks across this site. It's about the smallest useful program I have ever written, and it has earned its keep every day for the last month.

freedom-cms: a Rust HTTP server that lets me click-edit any element on this site, then autocommits the change to git. About 2,800 lines. Replaces a category of CMS for one specific use case (single editor, files-as-source-of-truth, deploy-via-git).

In all three cases, I am the user. I shipped them because I wanted to use them. The fact that they exist as binaries means I can install them on a new machine in 10 seconds.

Where I still reach for a different language

If the workload is "talk to the LLM SDK and orchestrate calls," I still reach for TypeScript. The Anthropic SDK and OpenAI SDK have first-class TS bindings, the streaming-response patterns are well-trodden, and I'm not in the language because of its language properties; I'm in it because of its ecosystem.

If the workload is "data analysis and one-off scripting," I still reach for Python. Pandas exists. Polars is great but Pandas exists more. For a one-shot CSV crunch, I do not want to write Rust.

If the workload is "frontend code that has to run in a browser," obviously I'm not writing Rust. There's WASM, but that's a different post.

The Rust default applies specifically to: CLIs, MCP servers, long-running background workers, anything that should be a single binary and live in /usr/local/bin. For that shape of program, Rust is the boring correct choice, and I'm done apologizing for it.

Next up