Rust vs Go for Backend APIs, When Each One Wins
TL;DR — Go wins for team velocity, hiring, and the boring middle of CRUD services. Rust wins for hot-path performance, memory-safe systems work, and shared-state correctness. Neither is “better.” Pick by problem shape, not by tribe.
End of March. Almost a month of writing Rust seriously while continuing to ship Go in parallel. This is the comparison post I would have wanted at the start of March, written now that I have actual production code in both languages.
I’m not going to pretend to be neutral. I write more Go. Most teams should write Go. But Rust earns its keep where it earns its keep, and pretending otherwise is its own form of dishonesty.
The framework: matched workloads
To compare fairly, I built equivalent services in both:
- Workload: a billing-style API. CRUD on subscriptions/invoices, gRPC client to a payment provider, Postgres backing store.
- Go stack:
chirouter,pgxdriver,zaplogging, standard library otherwise. - Rust stack:
axum,sqlx,tracing, standard library otherwise. - Tests: equivalent in both, mostly integration tests hitting Postgres.
Both services in production-ready shape: containerized, healthchecks, graceful shutdown, structured logs, request IDs propagated.
Lines of code
| Concern | Go | Rust |
|---|---|---|
main.go / main.rs (boot) |
88 | 64 |
| Handlers layer | ~520 | ~480 |
| Domain logic | ~640 | ~600 |
| Tests | ~430 | ~390 |
| Total | ~1,680 | ~1,530 |
Surprisingly close. Rust’s terser expression handling (?, pattern matching, iterators) makes up for its more verbose types.
Compile times
| Operation | Go 1.17 | Rust 1.59 |
|---|---|---|
| Cold full build | 12 s | 4 min |
| Incremental (one file changed) | 0.8 s | 6 s |
| Cold test run | 18 s | 5 min |
| Incremental test run | 1.5 s | 8 s |
Rust loses badly here. Cold builds are nearly 20× slower. Even incremental builds are 6–8× slower. This is the single biggest day-to-day pain point of Rust for me.
cargo check (no codegen) is faster than cargo build for “does this compile?” feedback. Editor LSP integration helps. The pain is still real.
Runtime performance
Synthetic benchmark: a request that does an auth check, two DB queries (via prepared statements), one gRPC call to a stub service, JSON serialization of the response. Single instance, 4 CPUs, 8 GB RAM.
| Metric | Go | Rust |
|---|---|---|
| Throughput (req/s) | 18,400 | 27,800 |
| P50 latency | 4.2 ms | 2.8 ms |
| P99 latency | 18 ms | 11 ms |
| Memory (RSS, steady) | 180 MB | 95 MB |
| Memory (RSS, peak under load) | 410 MB | 130 MB |
| CPU at 10K rps | 65% | 38% |
Rust wins on every dimension. Not 10× though. ~1.5× throughput, ~1.5× lower latency, ~3× less memory. For a service that’s bottlenecked on database (which most services are), the difference is smaller.
The memory delta is more dramatic than the throughput delta because Go’s GC keeps a lot of slack memory and Rust doesn’t.
Ergonomics
What Go has that Rust doesn’t:
- Implicit interfaces. “If it has the methods, it satisfies the interface.” No
impl Trait for Typeboilerplate. - Goroutines and channels. Lighter mental overhead than async/await + Tokio + select.
- One way to do things. Rust often has 3–4 reasonable answers (“should this be
Stringor&str?” “Box or Arc?” “Option<Vec> or Vec ?”). Go has one. - Fast iteration. Compile + test cycles measured in seconds.
What Rust has that Go doesn’t:
- Sum types (enums).
Result<T, E>,Option<T>,enum Event { ... }— modeling state machines and error variants is way more natural. - Pattern matching.
matchexpressions are the most useful syntactic feature I miss in Go. - Compile-time guarantees beyond types. Borrow checker, lifetime checker, ownership. Bugs that exist at runtime in Go are compile errors in Rust.
- Functional iterators.
iter().filter().map().collect()reads better than the Go for-loop equivalent. Result+?for errors. Better thanif err != nilafter 50K lines of code.- No nil for non-pointer types.
Option<T>makes “absent” explicit.
I miss enums + match the most when I’m in Go. I miss goroutines + simplicity the most when I’m in Rust.
Ecosystem
Go’s ecosystem (2022):
- Standard library covers most needs (HTTP, JSON, crypto, database basics)
- Mature, boring third-party libs for everything else (chi, pgx, zap, viper)
- Library API stability culture is excellent
Rust’s ecosystem (2022):
- Standard library is intentionally minimal
- You pull in serde, tokio, sqlx, axum, tracing — all crates
- Each crate is excellent in isolation; integration is your problem
- API churn has slowed dramatically since the async stabilization
Neither ecosystem is bad. Go’s is more cohesive; Rust’s is more powerful per crate.
Hiring
Honest:
- Go: easy to hire for. Devs from Java, Python, Node pick it up in two weeks. Plenty of candidates. Onboarding to a Go codebase: days.
- Rust: hard to hire for. Limited talent pool. Senior dev from another language: 2–3 months to first real PR. Onboarding to a Rust codebase: weeks.
For a 5-person team shipping a normal SaaS backend, Go’s hiring profile alone justifies it. For a specialized team (compiler work, embedded, low-latency systems), the Rust hire pool is fine and the perf wins justify it.
When I’d reach for each
Reach for Go:
- CRUD APIs
- Internal tools
- Most backends where DB is the bottleneck
- Teams smaller than 10 people
- “We need to ship in 6 months” projects
- Anything where hiring matters
Reach for Rust:
- Hot-path services where latency budget is sub-10 ms
- Memory-constrained environments (edge, embedded)
- Anything dealing with concurrent shared state that’s been buggy
- CLI tools that need to be fast and self-contained
- Systems work (proxies, embedded, crypto libraries)
- Compiler / parser / language tooling
- Cost-sensitive workloads where 3× memory savings pays for the dev velocity hit
Mixed shop:
Many teams will use both. Go for the boring 80%, Rust for the 20% that needs it. That’s where my team is heading. Two languages, two jobs.
The migration question
“Should we rewrite our Go service in Rust?”
Almost never. The 1.5× perf delta isn’t worth a 6-month rewrite when buying a bigger instance costs $200/month. Rewrite only when:
- You’ve genuinely outgrown Go’s perf ceiling (rare)
- Memory cost matters more than dev cost (cloud spend > engineer salaries)
- You’re staring at a class of bugs Go can’t prevent and tests aren’t catching
If you’re doing a greenfield service and either language could work, pick by team velocity (which usually means Go).
What “AI-native” rust looks like
A small aside since this whole site brands AI-native engineering. Rust’s compile times are genuinely worse for inner-loop AI-assisted dev — Claude Code or Cursor wait longer for build feedback. The borrow checker also produces error messages that LLMs sometimes misinterpret. Net: AI-assisted dev is more productive in Go right now. Probably narrows over time as Rust tooling and LLMs both improve.
This is not a reason to prefer Go forever. It’s a reason to factor it in.
Common Pitfalls
Picking Rust because “it’s faster.” Faster than what? Most services aren’t perf-bound. Pick Rust because you have a specific perf or correctness problem, not because of folklore.
Picking Go because “Rust is hard.” Rust is harder. That’s not a deciding factor — pick by what your problem needs.
Comparing the languages in benchmarks that don’t match your workload. “Rust is 30× faster at fibonacci” is irrelevant for a service that spends 80% of its time in Postgres.
Treating Rust as a Go replacement. They’re not in the same niche. Rust is closer to C++ in problem domain; Go is closer to Java/Python in problem domain. Different tools.
Believing the “Rust is hardest the first month, then fine” narrative. It’s easier after the first month. Still not as fast to iterate as Go.
Premature optimization at the language level. Service is slow → profile → fix the bottleneck (usually the database). 99% of the time you don’t need to switch languages.
Wrapping Up
Rust earns its complexity where it earns it. Go earns its simplicity where it earns it. My split going forward: Go for new services unless there’s a specific Rust reason; Rust for performance-critical components, CLI tools, and anywhere shared-state correctness matters. Wednesday: the March retro — what stuck, what didn’t, and where Rust actually goes in the stack from here.