Introducing Rust to a Go shop
A Go shop doesn't mean Go forever. Sometimes you hit a wall — performance, safety, or expressiveness — and Rust is the right tool. I've introduced Rust into two Go-centric teams. Both times it worked. Both times it was harder than expected.
Why we reached for Rust
The first time was a hot path in our billing engine. Go's garbage collector was causing p99 latency spikes at the worst possible moment — during a batch of financial transactions. We profiled, tuned, and rewrote the hot loop three times in Go. Each rewrite bought us two weeks of headroom before the graph resumed its upward march.
Rust gave us deterministic memory layout, zero-cost abstractions, and no GC pauses. The rewritten service held a flat p99 under 5x the original load. It was the right call.
The second time was a CLI tool that processed large CSV files. Go's single-threaded parsing was the bottleneck. Rust's zero-copy deserialization cut processing time from 45 seconds to 3. Again, the right call — but the integration cost was real.
The integration strategy
We didn't rewrite the service. We extracted the hot path into a Rust library and called it from Go via CGO. This is the key insight: you don't have to go all in.
// Rust side — compiled to .so
#[no_mangle]
pub extern "C" fn calculate_fees(
entries: *const Entry,
len: usize,
) -> f64 {
// zero-allocation fee calculation
}
// Go side — calls the .so via cgo
/*
#cgo LDFLAGS: -lfees
extern double calculate_fees(void* entries, int len);
*/
import "C"
The boundary is narrow, well-tested, and the rest of the service stays in Go — a language every engineer on the team already knows.
The costs no one talks about
Compile times. Rust compile times in a CI pipeline that was built for Go's 10-second builds are painful. Our CI went from 3 minutes to 12. We mitigated with caching, but it's still slower.
Hiring. If your team is all Go developers, adding Rust means either training people (months) or hiring Rust engineers (expensive, scarce). We did a mix — two people learned it, one experienced Rust developer joined.
Tooling. Go has one way to do everything. Rust has five. Formatting, linting, dependency management — all require new decisions and new CI configuration.
Code review overhead. Rust's borrow checker means that even experienced engineers write code that the reviewer has to think about carefully. Ownership, lifetimes, trait bounds — these aren't things Go developers have ever had to reason about.
When it's worth it
Rust is worth the cost when you have a measurable, profiled bottleneck that Go can't solve. It's not worth it for a CRUD API, a CLI tool, or anything where latency doesn't matter. Start with the narrowest possible integration — a library, a sidecar, a single function — and expand only when you have evidence that the investment is paying off.
The second time I introduced Rust, I made a rule: no new Rust code until the first integration has been in production for 3 months with zero incidents. That waiting period gave the team time to build confidence, and gave me time to build tooling. By the time we expanded, the process was smooth.
Don't adopt Rust because it's interesting. Adopt it because you measured a problem it solves.