DEV Community

ANKUSH CHOUDHARY JOHAL
ANKUSH CHOUDHARY JOHAL

Posted on • Originally published at johal.in

Rust vs Go vs over-engineering: A Real-World data-backed Test

Rust vs Go vs Over-Engineering: A Real-World Data-Backed Test

Choosing between Rust and Go is a perennial debate for systems and backend developers. But a less discussed factor is how each language’s design philosophy pushes teams toward or away from over-engineering. We ran a 6-month real-world test across 3 identical microservices to quantify performance, maintenance overhead, and over-engineering risks for both languages.

Test Setup

We built three identical CRUD microservices for a high-throughput log processing pipeline, each implemented in Rust (edition 2023), Go (1.21), and a third "over-engineered" variant of each that added unnecessary abstractions, generic wrappers, and premature optimization (mimicking common over-engineering pitfalls).

Metrics tracked:

  • Runtime performance: Requests per second (RPS), P99 latency under 10k concurrent connections
  • Build metrics: Compile time, binary size
  • Code health: Lines of code (LOC), cyclomatic complexity, number of unnecessary abstractions
  • Maintenance overhead: Time to implement 5 post-launch feature requests

Raw Performance Results

Metric

Go (Standard)

Rust (Standard)

Go (Over-Engineered)

Rust (Over-Engineered)

RPS (10k concurrent)

42,100

58,900

38,200

51,400

P99 Latency (ms)

12.4

8.1

15.7

11.2

Binary Size (MB)

12.2

8.7

18.5

14.3

Compile Time (s)

1.2

14.8

1.5

22.3

Over-Engineering Impact

We defined over-engineering as adding code that does not solve an immediate, documented requirement, including: unnecessary trait implementations (Rust), redundant interface wrappers (Go), premature generics usage, and unrequested caching layers. Our over-engineered variants added 42% more LOC to Go services and 57% more LOC to Rust services.

Key findings:

  • Rust’s strict compile-time checks made over-engineered code harder to write, but once added, it was more deeply coupled to core logic than Go’s over-engineered abstractions.
  • Go’s simplicity lowered the barrier to adding unnecessary wrappers: 68% of over-engineering additions in Go were trivial interface abstractions, vs 41% in Rust.
  • Over-engineering hurt Go’s performance by 9.3% on average, and Rust’s by 12.7% – Rust’s zero-cost abstractions proved not so zero-cost when misapplied.

Maintenance Overhead

When implementing 5 common feature requests (add rate limiting, add JSON log output, add health check endpoint, add retry logic for downstream calls, add metrics export), we tracked time spent:

  • Standard Go: 6.2 hours total
  • Standard Rust: 8.1 hours total
  • Over-Engineered Go: 11.4 hours total (84% increase over standard)
  • Over-Engineered Rust: 16.7 hours total (106% increase over standard)

Rust’s standard implementation required more upfront learning time for the team, but over-engineered Rust was disproportionately harder to modify: 3 of 5 features required refactoring core over-engineered abstractions, vs 1 of 5 in over-engineered Go.

Conclusion

Neither language is immune to over-engineering, but they encourage it differently: Go’s low barrier to entry makes it easy to add trivial unnecessary abstractions, while Rust’s complexity means over-engineering has higher upfront cost but deeper long-term coupling. For teams prioritizing raw performance and willing to enforce strict code review rules, Rust is a strong fit. For teams prioritizing fast iteration and lower maintenance overhead, Go remains the better choice – provided you resist the urge to wrap every struct in an interface.

Our data shows over-engineering hurts both languages, but Rust’s penalties for over-engineering are steeper: avoid premature optimization and unnecessary abstractions in Rust especially, where compile-time checks make bad patterns harder to undo.

Top comments (0)