The question of how we adopted Rust in a Go microservices stack without committing to a rewrite comes up often in engineering circles. In 2026, the Rust-versus-Go debate has matured. Both languages are excellent for backend work, and teams like ours are finding pragmatic ways to combine them. Our approach was to build a Rust sidecar for the most latency-sensitive part of our platform, run it beside our existing Go service, and measure whether Rust’s reliability claims held up under real traffic. Here is what we learned.
Why a Second Systems Language Made Sense
Go has powered our platform for over five years. Its simplicity, fast compilation, and goroutine-based concurrency make it a great default. But one workload kept exposing a weakness: a protocol normalization layer that processes every incoming request. The service decrypts JSON payloads from legacy clients, converts field types, applies validation rules, and forwards normalized payloads downstream. In Go, the service worked, but profiling showed that garbage collector pauses were causing p99 latency spikes that our customer-facing SLAs could not tolerate.
We did not want to rewrite that service. It is thousands of lines of well-tested Go code. Instead, we extracted its hottest validation path into a separate component — our first Rust sidecar.
The Sidecar Pattern as an Adoption Strategy
A sidecar runs alongside the main application process and handles a narrow set of responsibilities. Service meshes use this pattern for TLS, routing, and metrics. Our sidecar used the same architecture but was application-specific: it performed validation and transformation of JSON payloads.
The Go service and Rust sidecar communicate over a Unix domain socket. The Go binary accepts incoming requests, manages connections, and passes the payload to the sidecar. The sidecar transforms it and returns the result. If the sidecar crashes, the Go service detects the failure and falls back to its internal validation logic. That fallback made the adoption safe. The sidecar was an accelerator, not a dependency.
Choosing the Right Workload for Rust
Not every component is a good sidecar candidate. We chose the validation path because it matched Rust’s strengths:
- High throughput and predictable latency without a garbage collector.
- Complex nested enums and pattern matching that map naturally to Rust’s type system.
- Tight control over memory allocation in an environment with modest instance sizes.
The sidecar is about 2,100 lines of Rust, using tokio and serde. We deliberately avoided a web framework; the sidecar is not an HTTP server and has no routing or middleware. Its narrow scope kept the attack surface small and the binary around 8 MB compiled with panic=abort. We also considered WebAssembly, but the runtime overhead and FFI complexity were not worth it. A native process was simpler and faster.
How We Built and Deployed the Rust Sidecar
Deployment followed the sidecar pattern: we packaged both binaries into the same Docker image. The Go service is the entrypoint and spawns the Rust process at startup. A health-check port lets the orchestrator verify the sidecar is alive, and a heartbeat tells the Go service whether to use the fast path or fall back.
The build pipeline required integrating cargo into our Go-focused CI. We used cargo-chef with a rust:1.81-slim base image and pinned every dependency. The deliverable was a single image — no new infrastructure. Cross-compilation for ARM worked smoothly using the cross tool. Cargo’s dependency resolver sometimes picked conflicting versions, so we added a nightly CI job that runs cargo audit for known vulnerabilities.
The hardest part was caching Rust builds. Persisting the target directory and using sccache reduced CI build times from three minutes to about 40 seconds.
Testing Reliability with Real Traffic
Synthetic benchmarks are not enough to prove reliability. We ran a one-month shadow mode where the Go service duplicated production requests to the Rust sidecar but ignored the results. This let us compare correctness and performance without affecting users.
During shadow mode, the sidecar processed over 400 million payloads. We measured three things:
- Correctness: output parity with the Go implementation. Our harness compared SHA-256 hashes of the transformed payloads and surfaced any discrepancies.
- Latency: sidecar path speed recorded as histogram metrics.
- Stability: memory dwell time, CPU usage, and crash frequency.
The results were decisive. The sidecar cut p99 latency from 18 ms to 7 ms, a 61% reduction, while memory usage stayed flat at around 20 MB, compared with Go’s 40 to 90 MB allocation pattern. There were zero crashes.
Chaos Engineering the Sidecar
Reliability includes how the system behaves when the sidecar fails. We killed the sidecar process in a canary environment and used traffic control rules to inject packet drops on the Unix socket traffic.
The fallback worked, but the 250 ms timeout added too much latency. We reduced it to 50 ms and added a circuit breaker: after two consecutive failures, the Go service stops dispatching to the sidecar for a cooldown period. Graceful shutdown drains active work in about 120 ms.
Operational Lessons Learned
The operational lessons surprised us. First, Rust build times were the biggest annoyance; caching fixed that. Second, observability needs attention: the tracing crate and OpenTelemetry aligned well with our existing Go logs after a small mapping layer.
Third, team skill spread was manageable. Of eight engineers, two were proficient in Rust, four were intermediate, and two were new. The compiler’s error messages did most of the teaching, and the borrow checker was the only real hurdle. Profiling was easy because the sidecar is a separate process; we attached perf to it without touching the Go service.
When This Pattern Makes Sense
The sidecar pattern is ideal for teams with a hot path that needs lower latency or tighter memory, but who cannot justify a rewrite. It also serves as a low-risk trial for Rust: the fallback ensures continuity, and the results tell you whether the language fits your stack.
But it is not a universal answer. A Rust sidecar won’t fix database I/O bottlenecks, and every extra process hop adds a small overhead. Choose a workload that benefits from isolation and is not I/O-bound in a way that cancels the gains.
We adopted Rust in a Go microservices stack through a sidecar pattern that delivered measurable improvements without a rewrite. The validation path became faster, memory stayed flat, and the team gained confidence in Rust’s production readiness. The pattern is now our reference for evaluating new systems-level workloads — one that favors measured evidence over language preference.
