Backends — Go vs Rust
Sister project shop-two-backends ships the same shop endpoints twice — once in Go (chi + sqlc) and once in Rust (axum + sqlx). Each comparison below shows the actual source files side-by-side, with notes on what's worth taking away.
Goal: make a Go developer who reads both columns say "okay, I see why I'd switch for this kind of code" — without pretending Go does nothing well. See the matrix for the honest counter-cases.
Heads up: the sibling shop-two-backends
checkout was not found at build time, so the comparison code
panels will be empty. Clone it as a sibling directory and rebuild.
-
Typed SQL: sqlc vs sqlx::query!
baselineBoth backends serve the same /products endpoints. The contrast is in how the SQL is written and checked — sqlc generates Go from a .sql file offline; sqlx checks inline SQL against a live database at compile time.
-
Concurrent fan-out: errgroup vs JoinSet
concurrencyEach warehouse query sleeps a per-warehouse latency_ms to simulate a remote call. Sum of latencies = 400 ms; max = 150 ms. Both backends complete in ~150 ms — the test asserts < 400 ms to catch any regression to serial execution.
-
State machine: switch vs exhaustive match
correctnessA 7-state order lifecycle. The Rust transition table is a match with no catch-all — adding a new variant refuses to compile until handled. The Go switch has no such guarantee; new states silently get no transitions.
-
WebSocket broadcast: hand-rolled hub vs broadcast::Sender
asyncA single ticker per backend mutates an owned price cache and fans out updates to every connected WebSocket subscriber. tokio::sync::broadcast::Sender does this in 25 lines of hub code; the equivalent Go hub is ~80.
-
Discriminated unions: serde tag vs json.RawMessage
json modelingOne webhook endpoint, three event shapes (payment.succeeded, payment.failed, refund.created). Rust's serde reads the discriminator and lifts the right variant in one pass; Go has to do a two-step parse via an envelope of json.RawMessage fields.
-
sqlx::query! — the demo that converts skeptics
correctnessDelete a column from your migration and rebuild. sqlx::query! refuses to compile and tells you exactly which call site references the missing column. No tests, no DB roundtrip in production code, no surprise at 3am.