Zero-Cost Abstractions
Zero-cost means you do not pay for abstractions you do not use, and abstractions you do use can compile to code as efficient as hand-written loops.
Search across all documentation pages
Zero-cost means you do not pay for abstractions you do not use, and abstractions you do use can compile to code as efficient as hand-written loops.
let total: u32 = data.iter().filter(|x| x % 2 == 0).map(|x| x * 2).sum();When to reach for this: Choosing between iterators, generics, and manual loops without sacrificing clarity.
fn sum_even_double(slice: &[u32]) -> u32 {
slice.iter().filter(|n| *n % 2 == 0).map(|n| n * 2).sum()
}
fn sum_even_double_loop(slice: &[u32]) -> u32 {
let mut acc = 0;
for &n in slice {
if n % 2 == 0 {
acc += n * 2;
}
}
acc
}Inspect optimized assembly in --release; both often compile to identical machine code.
What this demonstrates:
Option/Result match compiles without heap allocation| Abstraction | Cost |
|---|---|
| Iterator chain | Often same as loop |
Option/Result | Stack-only, no boxing by default |
Generic fn | Monomorphized, inlined |
enum dispatch | Tagged union, no vtable unless dyn |
dyn Trait dynamic dispatchBox<dyn Error> in hot pathString (alloc + indirect call if not inlined)collect() without type hint - extra passes or allocations. Fix: use sum(), try_fold, or explicit destination type.dyn for convenience - vtable per call. Fix: generics or enum dispatch in hot paths.| Alternative | Use When | Don't Use When |
|---|---|---|
Manual for loop | Proven hotspot needs control | Default choice over iterators |
rayon parallel iterators | CPU-bound data parallel | Async I/O code |
no_std | Embedded | Full Tokio/Axum stack |
Usually equal in release; benchmark if critical.
No for Option<T> where T is sized; it's a niche optimization on stack.
Compile time and binary size grow with many generic instantiations; runtime per call is static dispatch.
When inlined, yes; capturing env may allocate and prevent inline.
Static dispatch, zero-cost like concrete return type when not trait object.
State machine on stack/heap per task; not free but no per-poll heap alloc in well-written futures.
Paste rustc LLVM IR or assembly for two versions side by side.
Often identical; bounds checks may optimize away with iterators.
Query plan optimizes pipelines; collect materializes - plan layer is lazy abstraction.
Plugin boundaries, FFI, or rare cold paths where binary size matters more than call speed.
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.
Reviewed by Chris St. John·Last updated Jul 19, 2026