Skip to content

← all backend comparisons

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.

Go (chi · sqlc · pgx)
Entry point — stdlib only for HTTP, signals, logging
go/cmd/server/main.go
// shop-two-backends not found at build time
5 direct deps. The rest is in the standard library.
go/go.mod
// shop-two-backends not found at build time
Rust (axum · sqlx · tokio)
Entry point — axum, tokio, tracing, tower all required
rust/src/main.rs
// shop-two-backends not found at build time
14 direct deps, each with feature flags to remember
rust/Cargo.toml
// 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.