tradeoffs
Where Go shines — the honest counter-page
Every other page on this site argues for Rust. This one doesn't. Five places where Go is the right call — compile times, onboarding cost, deployment artifact, stdlib breadth, and the everyday cost of writing a service.
// shop-two-backends not found at build time // shop-two-backends not found at build time // shop-two-backends not found at build time // shop-two-backends not found at build time What to take away
The other six pages are the pitch. This one is the counter-pitch — read it before you bet your team on the rewrite.
1. Compile times
The Rust Dockerfile in this repo has a whole pre-step that builds
a stub fn main() {} just to cache the dependency graph,
because a cold cargo build --release on this tiny
service still takes minutes. The Go Dockerfile has no such trick —
go build on the same source finishes in seconds. For a
service this size, the difference between "edit, save, see it run"
and "edit, save, go get coffee" is a real productivity tax that
compounds across a team.
2. Onboarding cost
A Go developer can read every line of main.go on
the left and tell you exactly what it does on day one — it's
net/http, context, signal,
and slog, all from the standard library. The Rust
main.rs assumes you've internalized
#[tokio::main], the EnvFilter /
SubscriberExt idiom, axum's Router +
State pattern, and that ? propagates
via anyhow::Result. None of that is hard; all of it
is weeks of reading before a junior is productive.
3. Deployment artifact
Both backends ship as a single binary into a distroless image
and that's lovely. But: Go's binary is built with
CGO_ENABLED=0 and lands on
distroless/static, the smallest base in the family.
Rust uses distroless/cc because tls-rustls
and friends still want a libc. Both images are small; the Go one
is smaller, and faster to produce, every time.
4. Standard library breadth
Look at the dependency lists on the right. The Go side lists five direct deps — chi (router), pgx (Postgres driver), sqlc-generated code, errgroup, websocket. Everything else — JSON, HTTP server, signals, structured logging, sync primitives — is stdlib. The Rust side needs axum + tower-http + tokio + tracing + tracing-subscriber + sqlx + serde + serde_json + anyhow + thiserror + uuid + chrono + futures-util before you've written a handler. Each of those is a maintainer, a release cadence, and a vocabulary. Go's stdlib decision pays off forever.
5. "I just want to ship a service"
Most backends are not stock-check fan-outs and broadcast hubs.
Most backends are GET /thing, POST /thing,
one DB call, one log line, ship it. For that workload, Go's lower
ceremony — no async colour, no lifetimes, no feature-flag soup in
Cargo.toml, no Arc<Mutex<_>>
to think about — is a feature, not a limitation. The Rust wins on
the other six pages are real, but they only matter when the work
reaches for them. If your shop is mostly CRUD, Go will get you
there with less rope to hang yourself with.
The honest summary: pick Rust when the workload is the comparison's right-hand column — concurrency that has to be correct, state machines that must be exhaustive, hot paths where the borrow checker earns its keep. Pick Go when the workload is "ship a competent HTTP service this quarter with a team that includes juniors." Both are good answers. The mistake is pretending one of them is always the answer.