Insights - What Rust taught my Go

A Rust review, an uncomfortable question: why does my Go forgive what Rust forbids? On transferring discipline between languages, as a pipeline instead of a resolution.

· Oliver Philippsen · 2 min read
What Rust taught my Go

Reviewing a Rust codebase, I was reminded what makes that language special: the compiler refuses. A forgotten error case? Won’t compile. An unhandled match arm? Won’t compile. Whole classes of mistakes are simply not expressible.

Then the uncomfortable question: why does my Go forgive all of that?

Go is more tolerant. An error can be silently ignored. A switch may forget cases. That doesn’t hurt immediately. It hurts in six months, at night, in production.

So we tried the transfer: not porting the language, but the guarantees. For every Rust property there is a checking tool in Go. Ignored errors: errcheck. Sloppy error comparisons: errorlint. HTTP requests without a context, response bodies never closed: noctx and bodyclose. Eighteen checks now run under golangci-lint, and the pipeline makes them binding: no release while any of them is red. Much of what Rust’s compiler forbids, our pipeline now reports.

Result in a single Go project: around 60 real corrections. None of them had ever caused visible damage. The best one: require assertions inside an HTTP mock handler. When the assertion fails, the wrong goroutine dies, and the test can’t even fail properly. Exactly the kind of mistake that later becomes a mysterious ticket.

You can’t port a language. You can port discipline.

And because it went so well: Python got the same treatment. Three projects, three languages, one shared rulebook, each in the language’s own dialect.

Honestly: we didn’t adopt every strict rule. goconst mistook test data like “John” for magic constants, prealloc optimizes microseconds no CLI will ever feel. Both are off, with a decision record holding the why. Strictness without judgment is just noise.

My Go didn’t get stricter. My pipeline did.

Related Posts