Skip to content

← all backend comparisons

correctness

State machine: switch vs exhaustive match

A 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.

Go (chi · sqlc · pgx)
switch — no compile-time exhaustiveness
go/internal/domain/order.go
// shop-two-backends not found at build time
Handler with SELECT … FOR UPDATE
go/internal/httpserver/orders.go
// shop-two-backends not found at build time
Rust (axum · sqlx · tokio)
match with no _ — exhaustive by construction
rust/src/domain/order.rs
// shop-two-backends not found at build time
Handler with FOR UPDATE
rust/src/routes/orders.rs
// shop-two-backends not found at build time

What to take away

Pretend you ship a new state — say Returned.

Rust: cargo check fails immediately in allowed_next, as_str, and FromStr. You can't even build until every match handles the new variant.

Go: the switch in AllowedNext hits its return nil fallthrough and silently treats Returned as having no allowed transitions. Tests with coverage on the old states all still pass. The bug ships.

Both backends use SELECT … FOR UPDATE inside a transaction so two concurrent requests can't both observe the same pre-transition state. Correct on both sides — but Rust's correctness is enforced by the compiler; Go's is enforced by the reviewer remembering to grep.