Back to blog

Why I Chose Rust for a Mobile Testing Tool (And What I Learned the Hard Way)

10 min read
EngineeringRust

If you look at the AI agent ecosystem in 2026, it's overwhelmingly Python. LangChain, CrewAI, AutoGen, most MCP server implementations — Python everywhere. When I started building Drengr, a Rust CLI tool for mobile automation, more than one person asked me why I was making my life harder for no reason. This is my honest answer.

I wanted a single static binary with no runtime dependencies. Something you could curl and run. No pip install, no virtual environments, no "which Python version do you have?", no Docker container wrapping a Node.js server wrapping a Python script. Just a binary. That constraint pointed directly at Rust, and it shaped everything that followed.

The Landscape: Why Python Dominates

Python's dominance in AI tooling isn't accidental. The ecosystem is extraordinary. Need to call an LLM? pip install anthropic. Need to parse a UI hierarchy? There's a library. Need async HTTP? aiohttp or httpx, both mature and well-documented. The time from idea to working prototype in Python is genuinely unbeatable.

I respect that. I've built plenty of things in Python and I'll build more. But for a developer tool that other people need to install and run reliably on machines I'll never see, Python's strengths become weaknesses. Every dependency is a potential version conflict. Every requirements.txt is a prayer that the user's environment cooperates.

The Alternative I Actually Considered

Go was the other serious contender. Single binary, fast compilation, good concurrency model. I prototyped a few hundred lines in Go before switching. The reasons were specific: Rust's type system is more expressive for modeling device states, the enum with data is perfect for representing transport layers (ADB | Simctl | Appium), and I wanted the performance characteristics of zero-cost abstractions when processing UI hierarchies with hundreds of elements.

Reasonable people would choose Go here. I don't think they'd be wrong.

The Learning Curve Was Real

I won't pretend Rust was easy. I'd written some Rust before Drengr, but nothing at this scale — about 6,300 lines across the project.

The borrow checker humbled me. Early on, I tried to hold a mutable reference to the device connection while simultaneously reading from the screen capture buffer. The compiler rejected it. I fought it for an hour before realizing the compiler was right — I was setting up a data race that would have been a subtle, intermittent bug in any other language. The fix was a cleaner architecture.

Async Rust was genuinely painful. Drengr uses tokio for async I/O — ADB connections, HTTP requests to the dashboard, WebSocket streaming. The interaction between async functions, lifetimes, and trait objects produced error messages that occasionally felt like they were written in a language I hadn't learned yet. I spent entire days on issues that would have taken minutes in Python.

The ecosystem has gaps. Need a mature, well-maintained library for parsing Android UI automator XML dumps? In Python, you'd find three options. In Rust, I wrote my own parser. It's not a lot of code, but it's code I'd rather not have written.

The Payoff

Here's my Cargo.toml release profile:

[profile.release]
lto = "fat"
opt-level = "z"
strip = true
codegen-units = 1
panic = "abort"

With LTO fat optimization, size-optimized compilation, symbol stripping, single codegen unit, and abort-on-panic, the Drengr binary ships at approximately 15MB. That's the entire tool — MCP server, ADB integration, screen annotation engine, OODA loop, network capture — in a file smaller than most node_modules folders.

Cold start is nearly instant. There's no interpreter to load, no JIT to warm up. When an MCP client spawns Drengr, it's ready to accept JSON-RPC calls in milliseconds. For a tool that gets spawned per-session, this matters more than I initially expected.

Installation is one line. curl -fsSL https://drengr.dev/install.sh | bash. The script detects your platform, downloads the correct binary, and puts it in your path. No runtime to install first. No package manager required. This is the developer experience I wanted, and Rust made it possible.

Memory usage is predictable. Processing a UI hierarchy with 200+ elements, annotating a screenshot, and serializing the response uses a known, bounded amount of memory. No garbage collector pauses, no memory spikes from reference cycles. For a tool that runs alongside resource-hungry emulators, this matters.

Trade-offs I'd Reconsider

Honesty demands this section.

Compile times during development are brutal. A clean build of Drengr takes about 90 seconds on my machine. Incremental builds are faster — usually 8-15 seconds — but when you're iterating on a new feature and hitting compile-run-test loops, those seconds accumulate into frustration. Python developers simply don't have this problem.

Some ecosystem gaps compared to Python's AI libraries. When I needed to add LLM integration for the OODA loop's decision-making, the Rust options were functional but less polished than Python's. I ended up making raw HTTP calls to the Anthropic API rather than using a high-level client library, because the Rust client libraries at the time didn't support all the features I needed.

Prototyping speed. The borrow checker catches real bugs, but it also slows down exploration. When I'm trying three different approaches to a problem, Rust demands I think through ownership at each step. In Python, I can hack together a proof of concept in an hour and decide if the approach is worth pursuing. In Rust, that same exploration takes a morning.

Was It Worth It?

Yes. Unequivocally.

For a CLI tool that needs to be fast, reliable, and zero-dependency — a tool that other developers install on diverse machines and expect to just work — Rust is the right choice. The investment in fighting the compiler pays dividends every time someone installs Drengr in 10 seconds and it runs without issues on their Linux CI server, their macOS laptop, or their colleague's machine with a completely different environment.

The binary is the product. No installer wizard. No dependency hell. No "works on my machine." Just a file that does what it's supposed to do.

If the tool is the binary, then the language that produces the best binary is the right language. For Drengr, that's Rust.

Would I use Rust for a web backend? Probably not — there are faster paths to production. For a data pipeline? Python, without question. But for a developer-facing CLI tool where installation friction is the enemy and reliability is the feature? I'd choose Rust again tomorrow.