Rust in the Loop: Why Fast Tooling Makes Coding Agents Up to 68% Faster
What happens when legacy Python and Node checkers are swapped for Rust and native binaries, measured across 60 repository benchmarks and 120 coding agent sessions.
- Author
- Anvil Palamattam
- Published
- Reading time
- 15 min
Inside one agent verification turn
What happens when a verification gate takes 2 seconds vs. 12+ seconds
A new generation of native developer tools has replaced much of the Python and JavaScript toolchain. In the standalone benchmarks below, on real open-source repositories, Ruff lints Python 22 times faster than Flake8 and formats it 88 times faster than Black. uv installs a 58-package lockfile 11.5 times faster than pip from a cold cache. Pyrefly and ty check types 12 to 52 times faster than Pyright and 2.5 to 6 times faster than mypy (though mypy’s warm cache still wins on a rerun with no changes). On the TypeScript side, oxlint and oxfmt check VS Code 67 and 99 times faster than ESLint and Prettier, and Microsoft’s TypeScript 7 (tsgo) type-checks it 4 to 6 times faster than tsc 6. All of these tools are written in Rust except tsgo, which is written in Go.
When someone proposes swapping those tools into a codebase to speed up AI coding agents, the skeptical response is almost always the same: Why bother? The model takes 45 to 90 seconds to think and stream tokens anyway. Saving eight seconds on a linter is noise.
On a back-of-the-envelope spreadsheet, that objection looks reasonable. If an agent runs a verification gate twice during a 70-second task, cutting the gate from 12 seconds to 2 seconds should save about 20 seconds of CPU time—helpful, perhaps, but hardly transformative.
Measured across 120 end-to-end coding agent sessions and 60 standalone hyperfine benchmark groups, the spreadsheet math turns out to be wrong. Swapping legacy Python and Node checkers for Rust and native binaries—while keeping the codebase, task prompt, model and test suite identical—won 23 of 26 non-tied task pairs (p = 8.80 × 10⁻⁵) and cut median task completion time by 13% to 68% in four of the six model-and-language pairs. The other two pairs were a near tie and a pair where too few sessions passed to compare (details below).
Two mechanisms explain why slow tools hurt coding agents much more than a one-shot spreadsheet predicts.
Why slow gates cost more than their wall time
When a human runs eslint && prettier --check && tsc --noEmit in a terminal and it takes 12 seconds, the human waits 12 seconds once before committing. Coding agents pay that tax differently for two reasons:
- Agents verify inside the edit loop, not just at the end. Across the 120 sessions, models ran full or partial verification checks 1 to 4 times per task—after an initial edit, after fixing a type or lint diagnostic, and before committing. Even in sessions that never spent a turn polling a background task (every Gemini 4 Argon session, and 80% of Gemini 3.8 Flash Python runs), a faster gate saved time on every edit-check-fix cycle: 21 to 26 seconds off the median Gemini 4 Argon TypeScript and Gemini 3.8 Flash Python tasks (
161 s → 140 sand96 s → 70 s), and 60 to 98 seconds on some multi-file refactors and API migrations. - Slow commands trigger asynchronous background spillover. Agent harnesses cannot block a conversation forever if a command hangs or starts a server, so they enforce synchronous command timeouts. The exact threshold depends on the harness:
- Short synchronous windows (about 10 seconds): The agent runner used in this study waits at most 10 seconds for a command to finish. A 2-second Rust gate finishes inside that window and returns its output in the same turn. A 12-second legacy TypeScript gate is still running when the window closes, so the runner turns it into a background task. What happens next depends on the model. Gemini 3.8 Flash and Gemini 3.5 Flash-Lite usually spent extra turns polling the task for its result, and each of those turns sends the whole conversation back through the model. Gemini 4 Argon never polled: its gate was backgrounded just as often, but it waited for the runner’s completion notice instead. It still paid for the slow gate in time, as its tool time nearly doubled (
17 s → 31 s). - Longer synchronous windows (120 seconds): Claude Code, for example, waits up to 2 minutes by default before timing out a synchronous
Bashcall (unlessrun_in_backgroundis set). On a small repository, a 12-second check stays under that ceiling. On a large repository—like Django (47.3 seconds incremental with Pyright) or VS Code (100.6 seconds incremental and 483.9 seconds cold with ESLint +tsc6 + Prettier)—legacy gates either consume most of the 2-minute window on every turn or blow straight past it, whereas the Rust and native stack finishes in 1.94 seconds on Django and 9.96 seconds on VS Code.
- Short synchronous windows (about 10 seconds): The agent runner used in this study waits at most 10 seconds for a command to finish. A 2-second Rust gate finishes inside that window and returns its output in the same turn. A 12-second legacy TypeScript gate is still running when the window closes, so the runner turns it into a background task. What happens next depends on the model. Gemini 3.8 Flash and Gemini 3.5 Flash-Lite usually spent extra turns polling the task for its result, and each of those turns sends the whole conversation back through the model. Gemini 4 Argon never polled: its gate was backgrounded just as often, but it waited for the runner’s completion notice instead. It still paid for the slow gate in time, as its tool time nearly doubled (
In the 120-session benchmark, none of the 60 sessions using the Rust and native gate had a command backgrounded. Under the legacy gates, the gate was backgrounded in all 30 TypeScript sessions and 21 of 30 Python sessions (in Python, usually the first run, which builds the environment). Whether that cost extra turns depended on the model: 10 of 10 Gemini 3.8 Flash and 9 of 10 Gemini 3.5 Flash-Lite TypeScript sessions polled for the result, while Gemini 4 Argon waited for the completion notice every time.
Study 1: 60 standalone tool benchmarks on real repositories
Before full agent loops, the tools themselves were measured with hyperfine: 10 timed runs per command, pinned repository commits and tool versions, and 8 threads pinned to 8 cores of an AMD Ryzen 9 5900X. Four real open-source repositories span two orders of magnitude in size:
- Python: Rich (45,800 lines of Python) and Django (460,700 lines of Python).
- TypeScript: Hono (74,800 lines of TypeScript) and VS Code (3,640,000 lines of TypeScript).
In an agent loop, the number that matters most is the composite per-turn gate: running the linter, type checker and format check together after the agent edits a single file.
| Codebase & mode | Legacy gate | Rust / native gate | Wall (legacy → Rust) | Speedup |
|---|---|---|---|---|
| Django (461k Py) · incremental | flake8 + mypy + black |
ruff + pyrefly + ruff format |
17.4 s → 1.94 s | 9.0x |
| Django (461k Py) · vs. Pyright | flake8 + pyright + black |
ruff + pyrefly + ruff format |
47.3 s → 1.94 s | 24.4x |
| Django (461k Py) · cold cache | flake8 + mypy + black |
ruff + pyrefly + ruff format |
40.3 s → 2.54 s | 15.9x |
| Rich (46k Py) · incremental | flake8 + mypy + black |
ruff + ty + ruff format |
3.61 s → 0.25 s | 14.4x |
| VS Code (3.64M TS) · incremental | eslint + tsc 6 + prettier |
oxlint + tsgo + oxfmt |
100.6 s → 9.96 s | 10.1x |
| VS Code (3.64M TS) · cold cache | eslint + tsc 6 + prettier |
oxlint + tsgo + oxfmt |
483.9 s → 33.4 s | 14.5x |
| Hono (75k TS) · incremental | eslint + tsc 6 + prettier |
oxlint + tsgo + oxfmt |
5.59 s → 0.34 s | 16.5x |
Notice where each stack lands relative to those synchronous command windows. On a 461,000-line Python repository like Django, a legacy incremental check takes 17.4 seconds with mypy or 47.3 seconds with pyright—long enough to background on every turn in a 10-second runner and stall multi-step edits in any harness. Replacing those three tools with ruff, pyrefly and ruff format --check brings the entire gate down to 1.94 seconds, comfortably inside a single synchronous tool call. Even on VS Code’s 3.64 million lines of TypeScript, an incremental gate drops from 100.6 seconds to 9.96 seconds (oxlint + tsgo + oxfmt).
Breaking the gate into its individual tools shows where the time goes. The last two rows, package install and code search, sit outside the per-turn gate but run often in agent sessions:
| Check & corpus | Legacy tool (median) | Rust / native tool (median) | Speedup |
|---|---|---|---|
| Python lint · Django cold | flake8 (10.7 s) |
ruff check (0.49 s) |
21.7x |
| Python format · Django cold | black --check (22.8 s) |
ruff format --check (0.26 s) |
87.5x |
| Python types · Django cold | mypy (6.82 s) / pyright (33.2 s) |
pyrefly (1.79 s) |
3.8x / 18.6x |
| Python types · Rich incr | mypy (1.27 s) / pyright (11.4 s) |
ty (0.22 s) |
5.8x / 52.3x |
| TS lint · VS Code cold | eslint (82.4 s) |
oxlint (1.23 s) |
66.8x |
| TS format · VS Code cold | prettier --check (222.6 s) |
oxfmt (2.25 s) |
98.7x |
| TS types · VS Code cold / incr | tsc 6 (178.8 s / 26.0 s) |
tsgo (29.9 s / 6.47 s) |
6.0x / 4.0x |
| Python install · 58 packages, cold | pip (17.6 s) |
uv sync (1.52 s) |
11.5x |
| Code search · VS Code | grep -rn (0.25–0.49 s) / find (62 ms) |
rg (65–69 ms) / fd (50 ms) |
3.6–7.5x / 1.2x |
These speedups don’t come from checking less. The lint comparisons use matched rule sets (Flake8’s E4,E7,E9,F rules, and 68 rules shared between ESLint and oxlint), and the tools flag comparable volumes of findings: 12,767 for oxlint against 16,122 for ESLint on VS Code. Searches exclude .git on both sides. Two caveats apply. Package install over the network is noisy (one pip run hit a retry spike), so the median is reported. And the benchmarks ran on a shared host while agent sessions were also running; legacy and native commands ran back to back in the same hyperfine invocation, so both saw the same background load.
Study 2: 120 end-to-end coding agent sessions
To see how much of that standalone speedup survives inside a real agent loop, the second study uses two multi-domain application repositories—an 83,000-line Python codebase and a 100,000-line TypeScript codebase—along with five multi-file engineering tasks per language:
bug_fix: Diagnose and repair a cross-module state bug.feature: Implement a new domain feature across service and repository layers.refactor: Restructure shared records and call sites across modules while preserving behavior.error_paths: Add structured error handling across a multi-step pipeline.api_change: Migrate paginated service methods and callers across the codebase.
Every session received the exact same repository, task prompt and visible test suite, and was graded against a hidden held-out test suite after committing to main. The only difference between the two arms was what sh scripts/gate.sh executed:
- Python — Rust gate (
py-rust, 3.1 s cold / 1.6 s warm):uv+ruff format --check+pyrefly+ruff check+pytest. - Python — Legacy gate (
py-legacy, 12.0 s cold / 2.9 s warm):pip+black --check+mypy+flake8+pytest. - TypeScript — Rust / native gate (
ts-rust, 2.2 s cold / 2.0 s warm):oxfmt+tsgo(TypeScript 7 native) +oxlint+node --test. - TypeScript — Legacy gate (
ts-legacy, 12.4 s cold / 12.7 s warm):prettier --check+tsc6 +eslint+node --test.
The first Python gate run in a session builds the environment with uv or pip; later runs only check it, which is why the warm Python times are much lower than the cold ones. tsgo is the one tool here that isn’t written in Rust: it’s Microsoft’s Go port of the TypeScript compiler.
The study ran three Gemini model tiers spanning frontier reasoning to lightweight execution—Gemini 4 Argon, Gemini 3.8 Flash, and Gemini 3.5 Flash-Lite—for 10 sessions per language-and-gate cell (5 tasks × 2 repetitions = 40 sessions per model, 120 sessions in total).
Median time to a correct result
The five model and language pairs with a median in both arms
Across all 30 paired model × language × task cells, the Rust and native gate was faster in 23 cells, the legacy gate was faster in 3 cells, and 4 cells tied (tasks where Gemini 3.5 Flash-Lite did not reach a passing solution in either arm). A two-sided exact sign test gives p = 8.80 × 10⁻⁵, with a median finite time-to-correct reduction of 21.7 seconds per task.
Five of the six model-and-language pairs reached a median time-to-correct in both arms. Four of them finished 13–68% faster with the Rust and native gate. The fifth, Gemini 4 Argon on Python, was a near tie: 174 s with the Rust gate against 171 s with the legacy gate. The Rust gate won three of its five tasks, but the median moved slightly the other way. For that pair the gate was a small share of the session: the faster gate saved about 9 seconds of gate time, while model time ran 13 seconds longer in the Rust arm (156 s against 143 s). In the sixth pair, Gemini 3.5 Flash-Lite on Python, fewer than half the sessions passed in either arm (5 of 10 and 4 of 10), so neither arm has a median time-to-correct. The pass-only medians in the table (30 s and 38 s) count only the sessions that succeeded.
| Model & language | Gate | Correct | Median time | Gate / tool time per session | Turns | Backgrounded / polled |
|---|---|---|---|---|---|---|
| Gemini 4 Argon · Python | Rust | 10/10 | 174 s | 2.9 s / 20 s | 13.0 | 0 / 0 |
| Gemini 4 Argon · Python | Legacy | 10/10 | 171 s | 11.5 s / 28 s | 14.0 | 8 / 0 |
| Gemini 4 Argon · TypeScript | Rust / native | 10/10 | 140 s | 2.0 s / 17 s | 13.0 | 0 / 0 |
| Gemini 4 Argon · TypeScript | Legacy | 10/10 | 161 s | 12.1 s / 31 s | 15.5 | 10 / 0 |
| Gemini 3.8 Flash · Python | Rust | 10/10 | 70 s | 7.5 s / 9 s | 25.0 | 0 / 0 |
| Gemini 3.8 Flash · Python | Legacy | 10/10 | 96 s | 20.1 s / 23 s | 28.5 | 8 / 2 |
| Gemini 3.8 Flash · TypeScript | Rust / native | 10/10 | 81 s | 7.6 s / 9 s | 29.0 | 0 / 0 |
| Gemini 3.8 Flash · TypeScript | Legacy | 10/10 | 104 s | 36.8 s / 45 s | 28.0 | 10 / 10 |
| Gemini 3.5 Flash-Lite · Python | Rust | 5/10 | — (30 s pass) | 6.9 s / 8 s | 19.0 | 0 / 0 |
| Gemini 3.5 Flash-Lite · Python | Legacy | 4/10 | — (38 s pass) | 17.8 s / 21 s | 20.0 | 5 / 5 |
| Gemini 3.5 Flash-Lite · TypeScript | Rust / native | 8/10 | 31 s | 5.5 s / 6 s | 16.5 | 0 / 0 |
| Gemini 3.5 Flash-Lite · TypeScript | Legacy | 6/10 | 97 s | 36.2 s / 41 s | 25.0 | 10 / 9 |
Three details in this table explain why developers feel the difference immediately when they switch toolchains:
1. Slow gates multiply tool wait time across every edit cycle
Look at Gemini 4 Argon and Gemini 3.8 Flash. Both solve all 20 Python and TypeScript tasks in both arms. On Gemini 4 Argon TypeScript, swapping the legacy gate (prettier + tsc 6 + eslint, 12.1 s of gate time per session) for the Rust and native gate (oxfmt + tsgo + oxlint, 2.0 s) cuts total tool time nearly in half (31 s → 17 s), drops median turns from 15.5 to 13.0, and cuts median completion time from 161 s to 140 s.
On Gemini 3.8 Flash, which runs the full gate 3 to 4 times per session, the legacy TypeScript stack spends nearly five times as long in the gate (36.8 s against 7.6 s) and spills into a background task in 10 of 10 TypeScript sessions. Switching to the Rust and native gate cuts median completion time from 96 s to 70 s in Python and from 104 s to 81 s in TypeScript.
Output tokens moved less consistently. They fell in four of the six pairs, by 20% to 54%, and rose by 9% to 12% in the other two (Gemini 3.8 Flash TypeScript and Gemini 3.5 Flash-Lite Python).
2. The smallest model lost the most time to slow gates
Look at Gemini 3.5 Flash-Lite on TypeScript. Under the legacy gate, every session had its gate backgrounded and 9 of 10 spent turns polling it, median turns rose to 25.0, output tokens more than doubled (5,411 against 2,470 with the Rust gate), and median time-to-correct reached 97 seconds. With the 2-second Rust and native gate, it never had a command backgrounded and finished in 16.5 turns and 31 seconds, a 68% reduction.
Correctness barely moved. Both arms produced working code in all 10 sessions. Under strict grading, which also fails a session that edits files outside the task’s scope or skips the required commit, the Rust arm passed 8 of 10 against 6 of 10. With ten sessions per arm, that gap is too small to read as a real difference (two-sided Fisher’s exact test, p ≈ 0.63).
3. On multi-step refactors and API migrations, the savings compound
When a task only requires one gate run at the very end, a fast gate saves 10 to 20 seconds. When a task requires iterative verification across several modules—such as refactor, error_paths and api_change—the gap widens dramatically:
- On Gemini 4 Argon (TypeScript),
refactordropped from 175 s to 115 s (−60 s) andapi_changedropped from 439 s to 341 s (−98 s); on Gemini 4 Argon (Python),featuredropped from 177 s to 137 s (−40 s) andrefactordropped from 130 s to 114 s (−16 s). - On Gemini 3.8 Flash (Python),
error_pathsdropped from 125 s to 75 s (−50 s) andapi_changedropped from 208 s to 119 s (−88 s).
These measurements are one half of a broader shift. Coding agents made slow inner-loop tools expensive, and over the same period they made Rust itself much cheaper to write and migrate to. The companion post, Why the AI Era Flipped the Economics of Rust, covers that second half and the agent-driven Rust migrations behind it.
The practical rule: budget 3 seconds for your agent gate
You do not need to rewrite your Python or TypeScript application in Rust to capture this speedup. Put Rust and native tools inside the verification gate your coding agent runs on every turn:
- In Python: Replace
pip+black+flake8/pylint+mypy/pyrightwithuv+ruff format --check+ruff check+pyrefly(orty). On a 460k-line repo, that drops an incremental gate from 17–47 seconds to under 2 seconds. - In TypeScript: Replace
prettier+eslint+tsc6 withoxfmt+oxlint+tsgo(TypeScript 7 native). On a 75k–100k-line repo, that drops the gate from 6–12 seconds to 0.3–2.0 seconds; even at 3.6 million lines, an incremental gate stays right at 10 seconds instead of 100.
Keep the entire pre-commit or verification gate under 3 seconds warm. Below that line, every check returns synchronously inside the turn that called it, and your coding agent spends its tokens writing code instead of polling background tasks.