Skip to main content

On This Page

Draft / Scheduled Content

This article is a draft or scheduled for future publication. The content is subject to change.

Rust is Great, but You're Probably Using it for the Wrong Reasons

6 min read
Share

The Hype Train

For nearly a decade, Rust has topped Stack Overflow’s survey as the “most loved” programming language.

It’s easy to see why. Rust is a technical masterpiece. It offers the performance and control of C/C++ but with guaranteed memory safety at compile time, completely eliminating classes of bugs like null pointer dereferences, buffer overflows, and data races. It does all of this without a garbage collector.

It is, without a doubt, the right tool for writing web browsers, operating system kernels, databases, compilers, and high-performance game engines.

But the hype has overflowed its banks.

Now, we see teams choosing Rust to build standard JSON-over-HTTP APIs, simple e-commerce checkouts, and basic CRUD applications. Developers justify this by citing “zero-cost abstractions,” “low memory usage,” and “future-proofing for scale.”

This is architectural malpractice.

Choosing Rust for a typical web application is trading developer productivity—the most expensive resource in a company—for CPU cycles you don’t need and memory safety you could have gotten for free with a garbage collector.

The Borrow Checker Tax

Rust achieves memory safety without a garbage collector by using a system of ownership, borrowing, and lifetimes. Every resource has a single owner, and the compiler strictly checks when references are passed around to ensure they don’t outlive the data they point to.

This is a beautiful theoretical model. But in practice, it is a massive cognitive tax.

In a garbage-collected language (like Go, Python, Java, or Node.js), if you want to pass a user object to three different helper functions, you just do it:

// JavaScript: zero cognitive effort, GC handles the memory
const user = { name: "Alice" };
sendWelcomeEmail(user);
logSignup(user);
saveToAnalytics(user);

In Rust, you have to decide:

  • Do I pass a reference (&User)?
  • Do I clone the user object (user.clone()), sacrificing performance?
  • Do I wrap it in a smart pointer (Rc<User> or Arc<User>)?
  • If I pass a reference, does the compiler need explicit lifetime annotations ('a) to prove the reference won’t outlive the struct it resides in?

You end up fighting the borrow checker, refactoring your data structures not because the business logic changed, but because you need to satisfy the compiler’s ownership rules. Code that would have taken ten minutes to write in Go takes two hours in Rust.

The Myth of Scale

“But Rust is so fast! It will save us thousands in server bills!”

This is the standard justification. Let’s look at the reality.

For 99% of web applications, the bottleneck is not the CPU. The bottleneck is the network and the database.

When a user requests a page, your application:

  1. Receives the HTTP request.
  2. Queries PostgreSQL (taking 5-50ms).
  3. Reads from Redis (taking 1-5ms).
  4. Serializes the data to JSON (taking 0.1ms).
  5. Sends the HTTP response.

Whether your serialization takes 0.1ms (Node.js) or 0.001ms (Rust) is completely irrelevant when the database query took 20ms. The user will not feel the difference. Your cloud bill will not change, because you are still paying for database instances, load balancers, and network traffic.

If your application is truly CPU-bound (e.g., you are doing real-time video encoding, cryptography, or running heavy machine learning models), then Rust is a great choice. But if you are just moving JSON from a database to a browser, Rust is an expensive hammer for a tiny nail.

The Garbage Collector is Your Friend

Rust developers treat garbage collection (GC) as a dirty word. They talk about “GC pauses” and “performance unpredictability” as if they are catastrophic events.

For systems programming, GC pauses are a problem. If you are writing a robotics control loop or a high-frequency trading platform, a 10ms GC pause can be disastrous.

For a web API, a 10ms GC pause is completely unnoticed.

Modern garbage collectors (like Go’s tri-color concurrent collector or Java’s ZGC) are incredibly optimized. They run concurrently with your application code, keeping pause times under a millisecond.

Garbage Collection vs. Borrow Checker
+-------------------------------------------------------------+
| Garbage Collector (Go, Java, Node.js)                       |
|   - Programmer writes logic quickly.                        |
|   - Runtime automatically cleans up memory.                 |
|   - Cost: Small memory/CPU overhead (milliseconds).          |
+-------------------------------------------------------------+
| Borrow Checker (Rust)                                       |
|   - Programmer spends hours satisfying compiler.             |
|   - Zero runtime overhead.                                  |
|   - Cost: Heavy developer time (hours/days).                |
+-------------------------------------------------------------+

By refusing to use a garbage collector, you are manually doing the work that a highly optimized runtime could do for you. You are trading your own high-value developer time to save a few megabytes of RAM.

The Ecosystem and Talent Trap

Choosing a language is also about ecosystem and hiring.

The Rust ecosystem is growing rapidly, but it is still small compared to Java, Node.js, or Go. Many third-party APIs, database drivers, and cloud services do not have first-class Rust SDKs. You will find yourself writing wrappers, implementing missing protocols, and rebuilding wheels.

Hiring is even harder. There are very few experienced Rust developers on the market, and they command a massive premium.

If you choose Rust, you will end up hiring developers who want to learn Rust, and they will write their first Rust codebase on your company’s dime. You will inherit a codebase filled with clone(), unwrap(), and complex lifetime workarounds written by developers who were still learning the ropes.

A Pragmatic Stack Selection

If you are choosing a language for a backend service, here is a pragmatic framework:

  • Use Go if you want a compiled, fast, low-memory language with a simple syntax that any developer can learn in a weekend. Go has a garbage collector, compiles in seconds, and is designed for network services.
  • Use Node.js / TypeScript if you want maximum developer speed and want to share types/code with your frontend.
  • Use Python / Ruby if you are building an early-stage MVP where iteration speed is the only metric that matters.
  • Use Rust if you are writing a CLI tool, a database engine, a proxy, a high-performance parser, or a service that runs at massive scale where saving 10% CPU saves millions of dollars.

Rust is a Special Tool

Rust is an incredible language. It is a joy to write when you need its specific capabilities.

But don’t choose it for fashion. Don’t choose it to make your resume look better. Don’t choose it because you hate garbage collection.

Choose it because your problem genuinely demands systems-level control, zero-cost abstractions, and compiler-guaranteed thread safety. If your problem is just a CRUD API, keep it simple.

Related Content