Deep dive
Decision table
| Job | Pick | Why |
| HTTP/gRPC service | axum or actix-web + tokio, tonic for gRPC | Mature, fast, tower middleware |
| CPU-parallel batch work | rayon | Work-stealing par_iter with no data races |
| Serialization | serde (+ serde_json, bincode, rkyv for zero-copy) | De facto standard |
| Database access | sqlx (async, compile-checked SQL) or diesel | Typed queries |
| Errors | thiserror in libraries, anyhow in binaries | Typed vs convenient |
| CLI | clap derive | Help/completions for free |
| Python extension | pyo3 + maturin | Fast hot loops under Python |
| Embedded | embassy, embedded-hal | Async on microcontrollers |
| GUI | Tauri (web UI), egui, Slint | Weakest area; no dominant native toolkit |
- Values live on the stack by default;
Box, Vec, String own heap memory; Rc/Arc for shared ownership (refcounted), RefCell/Mutex for interior mutability.
- Generics are monomorphized: zero-cost, but code size and compile time grow.
dyn Trait gives one copy with vtable dispatch.
- Iterators compile to the same loops as hand-written indexing; bounds checks are often elided, and can be removed with slices/iterators instead of
unsafe.
unsafe exists for FFI, SIMD and data structures the checker can't prove; keep it in small, documented modules and run Miri on them.
Concurrency model
- OS threads with
std::thread::scope for borrowing data across threads; channels (std::sync::mpsc, crossbeam, flume); atomics with explicit memory ordering.
- Async:
async fn compiles to state machines; an executor (Tokio multi-threaded, work-stealing) polls them over epoll/kqueue/io_uring. Futures are lazy and cancelled by dropping.
- Blocking inside async code stalls the worker thread: use
spawn_blocking for CPU work or blocking syscalls.
Code
use std::collections::HashMap;
use std::sync::mpsc;
use std::{fmt, fs, io, thread};
#[derive(Debug)]
enum AppError { Io(io::Error), Empty(String) }
impl fmt::Display for AppError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
AppError::Io(e) => write!(f, "io: {e}"),
AppError::Empty(p) => write!(f, "empty file: {p}"),
}
}
}
impl From<io::Error> for AppError {
fn from(e: io::Error) -> Self { AppError::Io(e) }
}
fn count_words(text: &str) -> HashMap<&str, usize> {
let mut m = HashMap::new();
for w in text.split_whitespace() {
*m.entry(w).or_insert(0) += 1;
}
m
}
fn main() -> Result<(), AppError> {
let paths: Vec<String> = std::env::args().skip(1).collect();
let texts: Vec<String> = paths.iter().map(fs::read_to_string).collect::<Result<_, _>>()?;
if let Some(i) = texts.iter().position(|t| t.is_empty()) {
return Err(AppError::Empty(paths[i].clone()));
}
let (tx, rx) = mpsc::channel();
// Scoped threads may borrow `texts`: the compiler proves they finish before it is dropped.
thread::scope(|s| {
for t in &texts {
let tx = tx.clone();
s.spawn(move || tx.send(count_words(t)).unwrap());
}
});
drop(tx); // close the channel so the receiver loop ends
let mut total: HashMap<&str, usize> = HashMap::new();
for part in rx {
for (w, n) in part {
*total.entry(w).or_insert(0) += n;
}
}
let mut top: Vec<_> = total.into_iter().collect();
top.sort_unstable_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(b.0)));
for (w, n) in top.iter().take(10) {
println!("{n:>8} {w}");
}
Ok(())
}
Release profile for services (Cargo.toml):
[profile.release]
lto = "fat" # cross-crate inlining, order of 5-20 % faster, slower link
codegen-units = 1
panic = "abort" # smaller, faster; any panic ends the process
debug = "line-tables-only" # usable backtraces and profiles
Efficiency
- Cost: no GC and no runtime: values live on the stack unless boxed, and the cost model is the C one. Heap allocation is "moderately expensive" (Rust Performance Book), and a debug build is far slower than release. Compile time is the other cost: monomorphization and whole-program optimization trade build minutes for runtime speed. Bound: allocation count and memory bandwidth at run time, compile time at build.
- Levers:
lto = "fat" or "thin" — 10-20 % or more runtime, smaller binary (Performance Book) — release builds that can afford longer links
codegen-units = 1 — fewer missed optimizations, at the cost of build time (Performance Book) — release builds
- profile-guided optimization — 10 % or more (Performance Book) — stable workloads with sample data
panic = "abort" — no unwinding code (Performance Book) — programs that never catch_unwind
- fewer heap allocations (reuse
Vecs, SmallVec, arenas) — less malloc/free time (Performance Book) — hot loops
- faster linker (
lld, mold) — shorter links (Performance Book) — edit-compile loop
- Naive → standard → state of the art:
cargo build (debug) · unoptimized, not for benchmarks
--release · optimized, default settings
--release with fat LTO, codegen-units = 1, PGO and a profile-driven allocator choice · each step is worth 10 % or more per the Performance Book
Build and deploy
cargo build --release --locked; static Linux binaries via x86_64-unknown-linux-musl target or cargo zigbuild; container FROM gcr.io/distroless/cc or scratch.
- Compile time: split into workspace crates, use
sccache, mold/lld linker; cargo check for the edit loop.
- Editions (2021, 2024) keep old code compiling; stable releases every 6 weeks.
- Tooling:
clippy, rustfmt, cargo-nextest, cargo-deny, cargo flamegraph, Miri.
Pitfalls
- Fighting the borrow checker with
clone() and Rc<RefCell<…>> everywhere; restructure ownership (indices into a Vec, arenas, message passing) instead.
- Holding a
std::sync::Mutex guard across .await; use short critical sections or tokio::sync::Mutex.
- Blocking calls (file I/O, heavy CPU,
std::thread::sleep) on Tokio worker threads.
- Unbounded channels and
tokio::spawn without backpressure: memory grows until OOM.
- Debug builds are 10-100x slower than release; never benchmark them.
- Over-generic APIs exploding compile times; prefer
dyn at module boundaries.
unwrap() on input-dependent paths in servers: a panic kills the task (or the process with panic = "abort").
Implementations
| name | lang | repo | install | verdict | licence | health | as_of | notes |
| rustc + cargo | Rust | rust-lang/rust | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh | — | — | — | — | compiler and std; cargo ships with it |
| rustup | Rust | rust-lang/rustup | brew install rustup or the sh.rustup.rs script | — | — | — | — | toolchain manager; rustup component add clippy rustfmt |
| clippy | Rust | rust-lang/rust-clippy | rustup component add clippy | — | — | — | — | lints |
| tokio | Rust | tokio-rs/tokio | cargo add tokio --features full | — | — | — | — | async runtime, work-stealing |
| rayon | Rust | rayon-rs/rayon | cargo add rayon | — | — | — | — | data-parallel iterators |
Sources
- Klabnik, Nichols, The Rust Programming Language, No Starch Press. book
- The Rust Project, The Rustonomicon. book · The Rust Reference. docs
- Nethercote et al., The Rust Performance Book. book
- Jung, Jourdan, Krebbers, Dreyer, RustBelt: Securing the Foundations of the Rust Programming Language, POPL 2018. paper
- Tokio project, Tokio tutorial. docs
- Cloudflare, How we built Pingora, 2022. post
- Google Security Blog, Memory Safe Languages in Android 13, 2022. post
- Linux kernel documentation, Rust. docs