Hey, dipshit. You know what catches your memory bugs before they become customer-facing archaeology, ships as a single binary, and doesn't require you to choose between performance and a runtime made of wet cardboard? Rust.
Yes, Rust. The language you keep pretending is "too hard" because the compiler had the audacity to inform you that your program was bullshit before production did.
You don't need another GC-tuned service slowly turning RAM into invoices. You don't need a JavaScript toolchain that looks like a ransomware note. You don't need Python duct tape around C extensions around a sad little cron job that everyone is afraid to touch.
You need Rust.
The compiler is your meanest senior engineer
Rust does not trust you. Good.
You should not be trusted.
You are a bag of caffeine and confidence wearing headphones, and yesterday you almost shipped a use-after-free because "it seemed fine." Rust looks at your clever little pointer dance and says: no, idiot, explain who owns this.
That is not bureaucracy. That is engineering.
Ownership. Borrowing. Lifetimes. The holy trinity of "maybe your server should not explode because someone sent a weird request while another thread was holding the wrong reference."
fn main() {
let name = String::from("asshole");
greet(&name);
println!("{name}");
}
fn greet(name: &str) {
println!("hello, {name}");
}
No GC. No null pointer. No surprise mutation from the other side of the codebase. No "well it only crashes under load." The compiler forces you to say what you mean.
Awful, I know.
Cargo is what package managers pretend to be
You know what Rust has?
One build tool.
One test runner.
One dependency resolver.
One formatter.
One linter.
One lockfile.
One command that actually means "build the goddamn thing."
cargo build
cargo test
cargo fmt
cargo clippy
cargo run
That's it. No Gradle séance. No Maven XML museum exhibit. No package.json script soup where npm run build:prod:real-final2 secretly invokes seven tools maintained by teenagers and venture-funded regret.
Cargo is boring in the way a torque wrench is boring. It does the job every time, and you stop thinking about it.
The standard library is small because you are supposed to make choices
Rust's standard library is not trying to be your entire application framework. Good.
It gives you the primitives that matter: collections, threads, synchronization, paths, files, I/O, time, errors, atomics, channels. The rest lives in crates because Rust is not pretending that the year is 2009 and one committee can predict every use case.
Need HTTP? Use axum, actix-web, or hyper.
Need serialization? serde.
Need async runtime? tokio.
Need CLI parsing? clap.
Need errors that don't look like a crime scene? anyhow for apps, thiserror for libraries.
[dependencies]
axum = "0.8"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
anyhow = "1"
There. That's your backend stack. Stop making a Confluence page about it.
A web service without the clown car
Here is an HTTP route.
use axum::{routing::get, Json, Router};
use serde::Serialize;
#[derive(Serialize)]
struct Status {
ok: bool,
message: &'static str,
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let app = Router::new().route("/", get(root));
let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await?;
axum::serve(listener, app).await?;
Ok(())
}
async fn root() -> Json<Status> {
Json(Status {
ok: true,
message: "ship it",
})
}
That is a real service.
Typed response. Explicit async. Structured errors. No decorators. No monkeypatching. No reflection swamp. No global framework object quietly accumulating state like a haunted doll.
You build it, you get a binary, you run the binary.
cargo build --release
./target/release/myapp
Incredible. Computing, apparently.
Errors are values, not jump scares
Rust does not have exceptions.
Good.
Exceptions are control-flow landmines wearing a trench coat. You call a function, and somewhere twelve frames below you, some library decides to yeet control flow through the ceiling because the config file had a typo.
Rust makes failure part of the type.
fn read_config(path: &str) -> anyhow::Result<String> {
let contents = std::fs::read_to_string(path)?;
Ok(contents)
}
See that ??
That is not "ceremony." That is a visible failure boundary. It says: this can fail, and we are propagating the error. No stack-unwinding improv theater. No hidden contract. No "surprise, this function throws six things, one of which only happens on Alpine Linux."
You wanted correctness. Correctness has syntax.
Concurrency without summoning demons
Rust lets you write concurrent code without quietly loading a shotgun and pointing it at your foot.
Threads? Fine.
use std::thread;
fn main() {
let handle = thread::spawn(|| {
println!("hello from another thread");
});
handle.join().unwrap();
}
Shared state? Wrap it properly.
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let count = Arc::new(Mutex::new(0));
let handles: Vec<_> = (0..10)
.map(|_| {
let count = Arc::clone(&count);
thread::spawn(move || {
*count.lock().unwrap() += 1;
})
})
.collect();
for h in handles {
h.join().unwrap();
}
println!("{}", *count.lock().unwrap());
}
The compiler knows what can cross thread boundaries. Send. Sync. Those are not buzzwords. They are the difference between "this is safe to share" and "enjoy your heisenbug, clown."
In other languages, data races are a production incident.
In Rust, they are a type error.
"But the borrow checker!"
Yes. The borrow checker.
The part that makes you confront the actual shape of your program instead of spraying references everywhere like a raccoon with a firehose.
Most borrow checker fights are not Rust being difficult. They are Rust revealing that your design has unclear ownership. You wanted three systems to mutate the same object at the same time from different conceptual layers and somehow "just handle it."
No.
Pick an owner. Pass a reference. Clone intentionally. Use Arc when there is shared ownership. Use Mutex when there is shared mutation. Use channels when you want message passing. Use arenas when the lifetime is bulk-scoped. Use indices when your graph wants to be a graph instead of a self-referential misery sculpture.
The compiler is not blocking you.
It is refusing to notarize your nonsense.
Performance is not a feature you staple on later
Rust is fast because there is no interpreter hiding under the bed. No VM. No garbage collector pausing the world like it just remembered it left the oven on.
You get predictable memory layout. Stack allocation when possible. Zero-cost abstractions when done right. Iterators that compile down to tight loops. Enums that model state without turning your program into a class hierarchy landfill.
enum JobState {
Pending,
Running { started_at: std::time::Instant },
Failed { reason: String },
Complete,
}
That is not just "nice syntax." That is illegal states being made unrepresentable.
Try doing that cleanly in a pile of JSON objects and vibes.
Deployment is still a copy command
Rust builds a binary.
You copy it.
You run it.
cargo build --release
scp target/release/myapp user@server:/usr/local/bin/
ssh user@server 'systemctl restart myapp'
There is your deployment pipeline.
Yes, you can use Docker. Yes, you can cross-compile. Yes, you can ship a tiny container. Yes, you can produce static-ish Linux builds if you know what you are doing.
But the core artifact is not a directory full of hopes. It is an executable.
Your production deploy should not require a runtime, a package manager, a framework CLI, and a priest.
"But Go is simpler!"
Yes.
Go is simpler.
A spoon is also simpler than a torque wrench.
Use Go when you want straightforward network services with low cognitive overhead and a team that needs to move fast without learning ownership semantics. Go is fine. Go is good. Go is a perfectly respectable language for building things that should exist by Friday.
But do not confuse "simple" with "more correct."
Rust gives you sharper tools. That means you can cut yourself during the first week. It also means you can build systems where memory safety, concurrency safety, correctness, and performance are not afterthoughts taped to the sprint board.
If you are writing a toy CRUD app, fine, use Go.
If you are writing infrastructure, security tooling, databases, runtimes, agents, parsers, compilers, proxies, CLIs, embedded systems, high-throughput services, or anything where "oops" should not be a valid failure mode?
Just fucking use Rust.
"But compile times!"
Yes, Rust compile times can suck.
Congratulations, you found a real complaint.
Now use incremental builds, sccache, cargo check, fewer monomorphized generic crimes, sane crate boundaries, and stop rebuilding the universe because you put everything in one procedural macro crate named core.
Also, let's be honest: half the people whining about Rust compile times are waiting six minutes for Docker to rebuild a Node image because npm install invalidated the wrong layer.
Spare me.
"But async Rust is hard!"
Sometimes.
Because async systems are hard.
Rust did not invent backpressure, cancellation, pinning, task scheduling, shared state, or the fact that your distributed system is a bag of partial failures in a trench coat.
Rust just makes more of the pain explicit.
Use tokio. Use axum. Use tracing. Do not write your own executor. Do not make every trait async because you saw one blog post. Do not infect your whole codebase with generic lifetime confetti because you wanted to avoid one allocation in a request path that does twelve database calls.
Be normal.
Rust lets you be precise. It does not require you to cosplay as a type theorist with a head injury.
The ecosystem is not tiny anymore
This is not 2016.
Rust has serious crates for web services, databases, serialization, tracing, parsing, cryptography, CLI apps, WASM, embedded, data processing, game engines, GUI experiments, fuzzing, property testing, and systems work.
Is every crate perfect? No.
Neither is your beloved framework, which currently depends on 1,400 packages and a maintainer whose pinned tweet says "burned out, taking a break."
At least in Rust, when the dependency compiles, you already know an entire class of dumb bullshit did not make it through the front door.
Just fucking use Rust
Stop pretending memory safety is optional.
Stop pretending data races are acceptable because "we have tests."
Stop pretending performance is premature optimization when your solution is an interpreted language glued to native extensions and a Redis cache you added because the first version fell over.
Stop pretending the compiler is your enemy because it will not let you smuggle undefined behavior into prod.
Open your editor.
Run:
cargo new myapp
cd myapp
cargo run
Write the thing.
Let the compiler hurt your feelings.
Fix the program.
Ship the binary.
The correct choice is not always the easiest one. Sometimes the correct choice is the one that refuses to let you lie.