Go to Rust: The honest migration guide that admits what you lose

4 min read 1 source clear_take
├── "The guide's real value is telling readers when NOT to migrate, distinguishing it from the saturated genre of triumphant rewrite posts"
│  ├── @top10.dev editorial (top10.dev) → read below

The editorial highlights that roughly half the guide's word count is dedicated to reasons against migration, which sets it apart from the typical 'smug latency chart with no discussion of what broke' rewrite blog post. This restraint is what makes the piece worth reading in an otherwise saturated genre.

│  └── Matthias Endler / corrode (corrode.dev) → read

Endler's consultancy-authored guide deliberately frames migration as a decision with costs, not an inevitability. By devoting substantial space to when teams should stay on Go, the guide positions itself as an advisory document rather than Rust advocacy.

├── "Goroutines have no clean equivalent in Rust — the concurrency mental model is the hardest part of the migration"
│  ├── Matthias Endler / corrode (corrode.dev) → read

The guide is blunt that `go func() {}` does not map cleanly to async Rust: developers must pick a runtime (usually Tokio) and inherit the function-coloring problem. While mechanical primitives like channels, `select!`, and `JoinSet` translate, the underlying mental model does not.

│  └── @top10.dev editorial (top10.dev) → read below

The editorial echoes that the mechanics of concurrency translate but the mental model is the real friction point. This is framed as 'the big one' among translation challenges, more consequential than error handling or interface/trait differences.

├── "Rust tooling has reached or surpassed Go's, with trade-offs in specific areas"
│  └── Matthias Endler / corrode (corrode.dev) → read

The guide argues `cargo` beats `go mod` for most workflows (though slower), `clippy` is an order of magnitude stricter than `go vet`, and `rustfmt` ends formatting debates the way `gofmt` did. Testing is at rough parity, with Rust ahead on property-based testing via `proptest` but behind on Go's table-driven test ergonomics.

└── "Go-to-Rust is now an established industry pattern, not an experimental curiosity"
  └── @top10.dev editorial (top10.dev) → read below

The editorial cites Discord's Read States rewrite (2020), Cloudflare's Pingora replacing Nginx, and Microsoft rewriting Windows components in Rust as evidence that the migration path has institutional precedent. This frames corrode's guide as documentation for a real, repeatable pattern rather than a niche interest.

What happened

Matthias Endler's consultancy, corrode, published a long-form migration guide titled *Migrating from Go to Rust* — and it hit 221 points on Hacker News within a day. That's notable because the genre is saturated: every other week another team writes a blog post about rewriting a Go service in Rust, usually with a smug latency chart and no discussion of what broke. Corrode's guide is different because it spends roughly half its word count telling you when not to migrate.

The guide walks through the actual translation patterns Go developers stumble over: error handling (`if err != nil` becomes `?`, but only after you've internalized `Result`), interfaces vs traits (Go's structural typing has no direct equivalent — you reach for trait objects and pay an indirection cost, or you go generic and pay in compile times), and the big one: goroutines. The guide is blunt that there is no clean mapping from `go func() {}` to async Rust. You pick an async runtime — Tokio in most cases — and you accept that the function-coloring problem is now yours. Channels survive the trip mostly intact; `select!` exists; `sync.WaitGroup` becomes `JoinSet`. The mechanics translate. The mental model does not.

The other half of the guide covers tooling parity. `cargo` is better than `go mod` for most workflows but slower. `clippy` is stricter than `go vet` by an order of magnitude. There is no `gofmt` debate because `rustfmt` exists and nobody fights about it. Testing is roughly at parity, with Rust pulling ahead on property-based testing via `proptest` and falling behind on the dead-simple ergonomics of Go's table-driven tests.

Why it matters

The Go-to-Rust pipeline is now a real pattern, not a curiosity. Discord did it for their Read States service in 2020 and got the canonical p99 latency chart. Cloudflare's Pingora replaced an Nginx fleet (technically C, not Go, but the gravity is the same). Microsoft has rewritten chunks of Windows components in Rust. The question stopped being "is Rust production-ready" around 2022 and is now "is the migration cost worth the operational win."

The honest answer in the corrode guide — and in the HN comment thread, which is unusually high-signal — is that it depends on what's actually slow. If your Go service is bottlenecked on I/O concurrency, network calls, or database round trips, Rust will not save you. Goroutines are extraordinarily good at this workload, and the Go runtime's scheduler is more mature than anything in async Rust. Several commenters reported migrating CPU-bound services and seeing 30-70% memory reduction with flatter tail latencies; one reported migrating an I/O-bound service and seeing essentially no improvement except a 3x increase in feature delivery time.

The borrow checker remains the long pole. Senior Go engineers report a 4-8 week productivity dip while their brain rewires from "the GC will sort it out" to "who owns this byte slice and for how long." Teams that survive this period describe it as permanently changing how they think about data flow — even when they go back to writing Go. Teams that don't survive it usually pick the wrong first project: a sprawling business-logic service with deep object graphs, instead of something with clear ownership boundaries like a parser, codec, or proxy.

There's a second-order effect worth naming. Rust's compile times are still the single biggest predictor of whether a Go team will stick with the migration. A Go developer accustomed to sub-second incremental builds will not enjoy waiting 90 seconds for a clean check on a medium codebase. The newer `cranelift` backend helps for dev builds; `sccache` helps for CI; neither closes the gap entirely. This is the honest tax.

What this means for your stack

If you're evaluating a Go-to-Rust migration, the corrode guide's implicit framework is useful: pick a service where the ownership model is obvious, the API surface is small, and the performance ceiling actually matters. Parsers, encoders, video pipelines, anything with a hot loop over bytes — these are good. Business logic services with sprawling DTOs and rich domain models are bad first migrations and will burn you.

Start with FFI, not a rewrite. Both languages have decent C ABI stories; you can carve out the hot path in Rust, expose it via `cgo`, and leave the orchestration in Go. This gives you the win without committing the whole org to a new toolchain. Discord-style full rewrites work when you have a team that already knows Rust and a service whose scope is bounded enough to finish in a quarter.

If you're staying on Go — which, for most teams, is the right call — read the guide anyway. The sections on error handling discipline and explicit lifetime thinking will improve your Go code. The corrode guide is one of the few migration documents that doesn't make you feel stupid for not migrating.

Looking ahead

The interesting frontier is not whether Rust eats Go; it's whether async Rust ever feels as good as goroutines for the workload Go was designed for. Project Loom did this for Java in two years. The Rust async story has been "almost there" for five. Until that gap closes, the language split will stay roughly where it is: Go for services that talk to other services, Rust for code that talks to bytes. The corrode guide is a useful map of the border.

Hacker News 425 pts 437 comments

Migrating from Go to Rust

→ read on Hacker News

// share this

// get daily digest

Top 10 dev stories every morning at 8am UTC. AI-curated. Retro terminal HTML email.