medium.com 6 min read Original Source

I Spent a Day on the “Python vs Rust” Fight in the AI Era. Rust Didn’t Win.

I recently read an article whose title got under my skin. The argument was simple and a little uncomfortable:

The main reason we love Python is that it’s easy to write_. But if an AI is writing the code anyway, that advantage disappears. So why not let it write Rust, which is faster and safer, instead?_

It’s a good question. “Easy to write” has been Python’s trump card for twenty years. If AI quietly removes that card from the table, the whole deck changes.

I didn’t want a vibes-based opinion. I wanted numbers and scars. So I built the same five things in Python and Rust, ran them on the same laptop, and wrote down what actually happened.

Five small programs, each one written twice:

  1. Fibonacci, to measure raw speed.
  2. A word-count tool, to test file handling and errors.
  3. A shapes library, to see how each language models data.
  4. A tiny JSON API, to feel the ecosystem.
  5. Rust called from Python, the plot twist.

Here’s what each one taught me.

Finding 1: Rust is fast, but the algorithm is faster than both

I started with the obvious one. Naive recursive Fibonacci, fib(35), same code in both languages.

<button data-code="# Python
def fib(n):
if n u64 {
if n

# Python
def fib(n):
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)
// Rust
fn fib(n: u64) -> u64 {
    if n < 2 { n } else { fib(n - 1) + fib(n - 2) }
}
# Python
def fib(n):
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)
// Rust
fn fib(n: u64) -> u64 {
    if n < 2 { n } else { fib(n - 1) + fib(n - 2) }
}

Same logic, line for line. Here’s the stopwatch, including the smart version: the loop instead of the recursion, written in plain Python.

Rust’s ~34x is real, and exactly what you’d expect. It compiles to machine code with no interpreter in the middle.

But look at the last two rows against each other. Iterative Python finishes in 0.003 ms. Recursive Rust, fully optimized, takes 35 ms. The slow language with the good algorithm beat the fast language with the bad one by roughly 10,000x.

Picking the right algorithm mattered hundreds of times more than picking the right language.

If you remember one thing from this whole article, make it that. Reaching for Rust to fix a slow O(2ⁿ) loop is like buying a faster car to win a race you could have skipped by taking the shortcut.

Finding 2: Rust’s compiler is a co-author who refuses to ship your bugs

This is where the two languages stopped feeling like dialects and started feeling like different philosophies. Three moments did it.

Moment one: the number that quietly lied.

I pushed both versions to fib(100). Python gave me the exact answer, because Python integers grow as big as they need to. Rust’s u64 is a fixed 64 bits, so it ran out of room.

Same code, two builds, and the release build hands back a featherweight where an elephant should be. It’s a scale that silently resets past its maximum and reports the wrong number with total confidence. Python never does this here. The lesson isn’t “Rust is dangerous.” It’s that Rust makes the size-versus-safety tradeoff your explicit decision, where Python just hides it.

Moment two: a bug that can’t compile.

In the shapes program I modeled a Circle, a Rectangle, and a function to compute area. Then I added a Triangle and “forgot” to handle it.

In Python, that’s a runtime surprise. The code runs fine until the exact moment a triangle shows up, and then it explodes, maybe in production, maybe on a Friday.

In Rust, I got this before the program ever ran:

error[E0004]: non-exhaustive patterns: `Triangle` not covered
error[E0004]: non-exhaustive patterns: `Triangle` not covered

It would not build until I handled every case. The compiler counted the shapes for me.

Moment three: the borrow checker.

I passed a string into a function and then tried to use it again afterward, a complete non-event in Python. Rust stopped me cold:

error[E0382]: borrow of moved value: `text`
error[E0382]: borrow of moved value: `text`

This is the famous “fighting the borrow checker,” and yes, it’s annoying at first. But it’s the same rule that lets Rust guarantee a whole category of memory bugs simply cannot exist, with no garbage collector running in the background.

Python finds your mistakes when the code runs. Rust finds them when the code compiles. One of those is a much cheaper place to find them.

A small surprise along the way: bytes aren’t letters

The word-count tool handed me a bug worth thirty seconds, because it bites people in both languages.

My counter said one of my files had 1130 characters. The built-in wc command said 1132. Two characters had vanished, or appeared, depending on who you believed.

The culprit was a single ” — ” (an em dash) sitting in a comment. It’s one character but three bytes. So the answer depends entirely on what you’re counting.

Python and Rust agreed: both count characters by default. The takeaway that stuck is that “length” is not one number. The day you handle real user names, emojis, or non-English text, bytes and letters stop being the same thing, and a silent off-by-a-few bug is waiting.

Finding 3: Python ships enough to start, then you graduate to dependencies anyway

For the JSON API, the difference showed up before I wrote a single endpoint.

Python ships a web server in its standard library. My whole API had zero dependencies:

from http.server import BaseHTTPRequestHandler, HTTPServer
from http.server import BaseHTTPRequestHandler, HTTPServer

Rust’s standard library has no web server. I had to assemble one:

cargo add axum      # the web framework
cargo add tokio     # the async engine
cargo add serde     # JSON handling
cargo add axum      # the web framework
cargo add tokio     # the async engine
cargo add serde     # JSON handling

That pulled in 52 packages and took 8 seconds to compile the first time, for “hello world.”

But here’s the honest version, because the obvious takeaway is half wrong. Nobody ships real Python on http.server. The moment this API needed anything serious, I’d reach for Flask or FastAPI, and those bring their own dependency trees that look a lot like Rust’s. Python’s actual edge isn’t “no dependencies, ever.” It’s that Python ships enough to start in the box, so the first ten lines cost you nothing.

And the dependencies Rust made me install paid me back. I described my request shape as a plain struct:

<button data-code="struct SumReq {
numbers: Vec,
}” data-toggle-ms=”1200″ aria-label=”Copy code” data-nosnippet=”” class=”code-copy-btn absolute right-3 top-1/2 -translate-y-1/2 size-8 p-1.5 flex items-center justify-center bg-black/50 text-white rounded-md transition-colors duration-200 cursor-pointer hover:bg-black/70″>

struct SumReq {
    numbers: Vec<f64>,
}
struct SumReq {
    numbers: Vec<f64>,
}

When someone sent a bad request, missing the numbers field, the framework rejected it automatically with a clear 422 error. I wrote no validation code at all.

In Python, I had to hand-write the checks:

try:
    numbers = req["numbers"]   # blows up if missing
except (KeyError, TypeError):
    send_error(400)
try:
    numbers = req["numbers"]   # blows up if missing
except (KeyError, TypeError):
    send_error(400)

So the pattern holds: Python gets you running in ten lines and trusts you to check your inputs. Rust makes you assemble the stack, then checks the inputs for you.

Finding 4: The plot twist, you don’t actually have to choose

This was the experiment that reframed the whole question.

I took the slow Python fib and rewrote only that one function in Rust. Then I imported it back into Python like any normal library, using a tool called PyO3:

<button data-code="import fastfib #

import fastfib   # <- this is compiled Rust
fastfib.fib_recursive(35)   # called exactly like Python
import fastfib   # <- this is compiled Rust
fastfib.fib_recursive(35)   # called exactly like Python

Same call. Same Python int going in and coming out. But the work runs in Rust.

The boundary even stayed safe. When I passed -1, I got a clean Python OverflowError, not a crash.

This isn’t a party trick. It’s exactly how some of the most popular Python packages are built today. Polars, Pydantic, Ruff, and Hugging Face’s tokenizers are all Python on the outside with a Rust core on the inside.

_The real-world answer to “Python or Rust?” is very often _“Python, with the hot 5% in Rust.”

The whole day on one page

If I compress five programs into the differences that actually mattered:

Neither column is “better.” They’re two answers to the same question: where do you want to pay? Python pays later, in exchange for speed today. Rust pays now, in exchange for fewer surprises later.

So, if AI writes the code, why use Python?

After a day of actually building things, here’s my honest answer.

The article had a point. AI does lower the cost of writing Rust. The borrow checker is far less scary when something else takes the first pass at satisfying it. That genuinely shifts the math, and I think more teams will reach for Rust because of it.

But “easy to write” was never Python’s only card. The day showed me what the others are:

  • The ecosystem. Decades of NumPy, pandas, PyTorch, Django. AI doesn’t write those for you.
  • The speed of changing your mind. When requirements are still moving, Python lets you reshape things in seconds. Rust makes you convince a compiler first.
  • The fact that most code isn’t the bottleneck. If your program spends its life waiting on a network or a database, Rust’s raw speed buys you nothing.

So here’s the cheat sheet I wish I’d had at the start:

My takeaway, in one line

Use Python to move fast and reach the ecosystem. Use Rust where speed or correctness must be guaranteed. And when one program needs both, remember you can have both.

AI writing the code doesn’t end that debate. It just makes the “use both” answer easier than it has ever been.