Vivian Voss

The Replacement: From PHP to Rust

rust architecture performance

The Replacement ■ Episode 05

After twenty years of PHP, I needed something new for backend work. The obvious choice, according to the consensus, was Go. Everyone said so. The job listings said so. The conference talks said so. I looked at Go. I spent two weeks with it. I chose Rust instead.

This is not a theoretical comparison. This is the reasoning of someone who writes server software for a living and had to pick a tool for the next decade.

Why Not Go

Go felt like a step sideways. Yes, it compiles to a single binary. Yes, it is fast. But the details accumulated into a pattern that I recognised from languages I was trying to leave behind.

The garbage collector. Someone else deciding when to pause your programme. That is the programming equivalent of a flatmate who takes out the rubbish whenever they feel like it, not when the bin is actually full. You have no control over the timing, no control over the duration, and the pause happens precisely when you can least afford it: under load.

if err != nil in every second line. Error handling from the 1970s, without the excuse of the 1970s. No enums. No sum types. No pattern matching. Generics arrived in 2022, a decade late and half-baked. The language calls this “simplicity.” What it actually does is push complexity into your application code, where it is harder to audit and easier to get wrong.

Why Rust

Rust felt like a step forward. The first thing you notice is the compiler. It does not merely reject your code. It teaches you.

It tells you what is wrong. It tells you why it is wrong. It tells you how to fix it. It gives you the example code. Which other language does that? C gives you “Segmentation fault” and wishes you luck. C++ template errors span five hundred lines. Go says err != nil and leaves you alone. PHP gives you a stack trace if you are lucky.

Rust refuses to compile until your code is correct. That is not a steep learning curve. That is a safety net with instructions.

When Your Code Is Wrong C Segmentation fault C++ 500 lines of templates Go if err != nil { ... } PHP Stack trace (maybe) Rust ✓ What is wrong ✓ Why it is wrong ✓ How to fix it ✓ Example code The compiler as private tutor Memory: Same Workload PHP-FPM: ~4 GB (100 workers) Rust + Tokio: ~150 MB A twentieth of the memory. Same job. No null. No exceptions. No garbage collector. One binary.

The Learning Curve

“But Rust is hard!” I hear this constantly. Allow me to offer a different perspective.

Week one and two: fighting the borrow checker. The compiler rejects your code with messages that are, admittedly, unfamiliar. You are accustomed to languages that accept your code now and punish you later. Rust punishes you now and accepts your code later. The adjustment is real. It is also temporary.

Week three: something clicks. Ownership, borrowing, lifetimes, these are not arbitrary obstacles. They are the rules that every other language enforces at runtime, with garbage collectors, with null pointer exceptions, with segmentation faults. Rust enforces them at compile time. The information is the same. The timing is different. And the timing is better.

After that: you do not want to go back. Once you have a compiler that prevents data races, null pointer dereferences, use-after-free errors, and buffer overflows before your code runs, every other language feels like driving without a seatbelt.

The Numbers

PHP-FPM allocates approximately 40 MB of RAM per worker process. A hundred concurrent requests means four gigabytes of RAM consumed by PHP alone, before your application has done anything interesting. Rust with Tokio, the async runtime, handles the same workload in approximately 150 MB total. That is a twentieth of the memory for the same job.

The arithmetic extends to server costs. If your PHP application needs a server with 16 GB of RAM, your Rust application needs one with 1 GB. If your PHP application needs four servers, your Rust application needs one. The reduction compounds with scale, and scale is where the invoices become interesting.

The Ecosystem

“But can I replace everything PHP does?” Yes. Actix, Axum, Rocket, mature web frameworks. Diesel, SQLx, database access with compile-time query checking. MiniJinja, Tera, Askama, templating engines. Serde, serialisation that is faster than most languages’ built-in JSON parsers. The Stack Overflow survey has named Rust the most admired language for eight consecutive years. The ecosystem is not catching up. It has caught up.

The Reduction

No null. No exceptions. No garbage collector pauses. No runtime dependencies. One binary. Copy it to the server. Run it. There is no interpreter to install, no runtime to version-manage, no dependency tree to resolve at deployment time. The binary is the deployment. The deployment is a file copy.

The Rust compiler taught me more about programming than any book. Ownership, borrowing, lifetimes, these are not Rust-specific concepts. They are universal concepts that other languages let you ignore. Rust does not let you ignore them. That is not a weakness. That is the point.

No null. No exceptions. No garbage collector. One binary. A twentieth of the memory. The compiler as private tutor. Rust is not hard. It is honest. And honesty, in the long run, is cheaper than pretending.