Performance of Rust Language [pdf]

Rust performance and safety overhead

  • Bounds checks usually cost a few percent, but can reach ~20% in some hot paths or block autovectorization.
  • Techniques to reduce bounds checks include range-based iterators, slicing, explicit assert!s, and carefully placed unsafe.
  • When C++ is compiled with strong hardening (_FORTIFY_SOURCE, sanitizers, hardened STL), it often becomes slower than Rust for equivalent safety.
  • Integer overflow checks are expensive but are disabled in Rust release builds by default; they can be enabled if desired.

Bounds checking, optimization, and proofs

  • Rust currently lacks a stable, explicit contract for carrying high-level invariants from HIR to MIR, making sophisticated proof-based elimination of checks difficult.
  • LLVM’s scalar evolution (SCEV) is language-neutral and can’t exploit Rust-specific invariants well.
  • Bounds-check hoisting in MIR is possible but thorny: the compiler must preserve exact panic behavior and messages, and must reconstruct information lost during iterator lowering.
  • Some bounds-check patterns can already be removed via explicit asserts that LLVM can prove.

Comparisons with C and C++

  • One position: Rust is roughly as fast as C; modern C++ can be faster in practice due to powerful metaprogramming enabling zero-cost abstractions.
  • Counter-position: raw compiler optimizations on LLVM IR are shared, so C, C++, Rust, and Zig should be comparable; differences come from ergonomics, LTO, and how code is written.
  • C++ templates and constexpr are praised for enabling high-performance generic algorithms; others argue they increase complexity and can bloat code.
  • Rust’s strict aliasing rules can theoretically outperform C/C++, but current backends underutilize this potential.

Other languages and metaprogramming

  • Zig and Nim are cited as having strong compile-time metaprogramming; Nim’s crypto library is claimed to outperform C via generated assembly.
  • Julia and Fortran are mentioned as highly performant in numeric domains, but Julia is not considered a systems language.
  • Refinement-type approaches (e.g., Flux for Rust) aim to statically eliminate bounds checks.

Panics, exceptions, and semantics

  • Rust panics are catchable (like C++ exceptions), so the compiler cannot freely “time-travel” panics (e.g., hoisting a bounds failure to loop entry) without changing observable behavior.
  • Different rewrites of an out-of-bounds loop change when and where the panic occurs, which matters if panics are caught.

Compile times and tooling

  • Multiple comments criticize Rust’s slow compilation for large projects, harming the edit–compile–run loop.
  • Mitigations discussed: splitting code into multiple crates for parallelism and caching, reducing heavy proc macros (notably serde), using faster linkers (lld, mold), and caches like sccache/kache.
  • Rust’s single-crate “translation unit” model limits parallelism within a crate; LTO trades compile speed for runtime speed.
  • Some expect compiler parallelism and infrastructure work to improve this over time.

Ergonomics and real-world experience

  • Several practitioners report that writing truly high-performance code is:
    • Easiest in C++ (templates, allocators, compile-time tricks),
    • Much harder in C (no integrated generics),
    • Rust encourages safer, cache-friendly patterns and avoids some C++ pitfalls (e.g., overuse of inheritance and vtables).
  • A recurring theme: there is “no performance of a language” in the abstract; real performance depends on compilers, tooling, and what programmers are willing and able to express ergonomically.