Zero-cost abstraction, a principle Rust inherited from C++ and applies pervasively, means using a high-level feature (an iterator chain, a generic function, a closure) produces the same machine code as if you had hand-written the low-level equivalent yourself — you don't pay a performance penalty for writing more readable, more abstract code. Combined with no garbage collector (memory is freed deterministically when its owner goes out of scope) and no runtime type checks that a monomorphized generic would otherwise need, Rust routinely benchmarks within a few percent of equivalent C or C++ code.
Iterators compile to the same code as a manual loop
The Rust Book makes this comparison directly, with an actual benchmark rather than just an assertion: a search function written as a hand-rolled indexed loop against the same one written as an idiomatic iterator chain. Their measured result was 19,620,300 ns/iter for the loop versus 19,234,900 ns/iter for the iterator version — the iterator chain came out very slightly faster, not merely tied, though the two are well within each other's noise margin. The Book's own conclusion is deliberately hedged rather than absolute: "in many cases, Rust code using iterators compiles to the same assembly you'd write by hand," crediting optimizations like loop unrolling and bounds-check elimination — not a blanket guarantee that every iterator chain matches every hand-written loop, but close enough, often enough, that there's no reason to avoid the more expressive style out of a performance worry.
// idiomatic — reads clearly
let sum: i32 = (1..=1000).filter(|n| n % 3 == 0).sum();
// manual — same performance, more error-prone to write correctly
let mut sum2 = 0;
for n in 1..=1000 {
if n % 3 == 0 {
sum2 += n;
}
}Deterministic memory, no garbage collector pauses
Because ownership determines exactly when a value is dropped (at the end of its owner's scope, verified at compile time), there's no garbage collector and no unpredictable collection pause — the cleanup code (freeing the heap allocation, closing the file handle, whatever the type's Drop implementation does) is inserted directly into the compiled binary at the precise point it's needed, rather than left for a collector to discover later. That cleanup code still runs and still costs real CPU cycles, the same as a manual free() would in C — "no overhead" describes the absence of GC bookkeeping and pause time, not that destructors are free to execute.
{
let data = vec![1, 2, 3]; // allocated here
// ... use data ...
} // data is deallocated HERE, deterministically, no GC involvedBenchmarking with criterion
The standard library's built-in benchmarking (#[bench]) is nightly-only; the community standard is the criterion crate, which runs statistically rigorous benchmarks on stable Rust and reports confidence intervals rather than a single noisy number.
use criterion::{criterion_group, criterion_main, Criterion};
fn bench_sum(c: &mut Criterion) {
c.bench_function("sum 1000", |b| {
b.iter(|| (1..=1000).filter(|n| n % 3 == 0).sum::<i32>())
});
}
criterion_group!(benches, bench_sum);
criterion_main!(benches);