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 placedunsafe. - 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.