background-shape
Rust vs Go for Backend APIs, When Each One Wins
March 28, 2022 · 7 min read · by Muhammad Amal programming

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: chi router, pgx driver, zap logging, 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 Type boilerplate.
  • 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 String or &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. match expressions 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 than if err != nil after 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.