Go is the default choice for infrastructure tooling in 2025. Prometheus, Grafana Agent, Telegraf, Docker, Kubernetes — the list is long. When we started OxiPulse, we seriously considered Go. We chose Rust. Here's why.
The core constraint: resource overhead
A monitoring agent runs on every server you own. On a $4/month VPS with 512 MB of RAM and one shared vCPU, a 200 MB RSS agent is not acceptable. The agent itself becomes a thing to monitor.
Go's runtime carries a garbage collector. For most workloads this is invisible. For a long-running daemon that allocates metric structs every 10 seconds, GC pauses are rare but non-zero. More importantly, Go's minimum RSS on a real workload tends to be 20–50 MB just for the runtime, heap, and goroutine stacks.
Rust has no runtime and no garbage collector. OxiPulse's RSS in steady state is under 8 MB. On a constrained edge node or a Raspberry Pi, that difference matters.
Single static binary, for real
Go produces statically-linked binaries by default — unless you use CGO, which most network and system libraries do. Cross-compiling a CGO binary for Linux ARM64 from macOS requires a full cross-compilation toolchain and is notoriously fragile.
Rust's static linking story is simpler. OxiPulse targets x86_64-unknown-linux-musl and
aarch64-unknown-linux-musl, which produce fully static binaries with zero shared library
dependencies. The same binary runs on Alpine, Debian, Ubuntu, RHEL — any Linux distribution.
The OpenTelemetry SDK
OxiPulse speaks OTLP natively. The official Rust OpenTelemetry SDK is mature, async-native (built on Tokio), and integrates cleanly with Tonic for gRPC. There is no need for a separate exporter process or a sidecar.
The Go OpenTelemetry SDK is also production-quality, so this was not a deciding factor on its own.
What Go would have been better for
We are not anti-Go. If OxiPulse needed:
- A large number of plugins with dynamic dispatch (Telegraf's model)
- Fast iteration on protocol integrations
- A big contributor community (Go's tooling ecosystem is excellent)
…Go would have been the right call. For a focused, single-purpose binary where memory footprint and startup time matter more than plugin breadth, Rust was the better fit.
The tradeoff we accepted
Rust has a steeper learning curve and a slower compile cycle. Our CI build takes longer than it would in Go. The borrow checker caught real bugs during development — which was the point — but it also slowed initial implementation.
For a tool that runs unattended on thousands of servers, correctness and efficiency outweigh developer convenience. That tradeoff made sense for us.