Home › Programming languages › Systems languages
guide low

Rust

Problem Is Rust the right language when you need native speed without a garbage collector and without memory-safety bugs? This guide weighs that against its learning curve and compile times.

C-level speed without a garbage collector, with memory and data-race safety checked at compile time; paid for in learning curve and compile time.

rustsystemsmemory-safetyownershipasynctokiocargo
Overview

Rust tracks at compile time who owns each value and who borrows it. Memory is freed when the owner goes out of scope, and the borrow checker rejects dangling pointers, use-after-free and data races before the program runs. You get C++-class performance without those bug classes, plus cargo, a toolchain that makes dependencies and builds boring.

Key facts

Use when

  • Latency-sensitive services, databases, proxies, game servers, CLIs, WASM, embedded, parsers of untrusted input.
  • Real uses by field: CDN proxying (Cloudflare Pingora, about 70 % less CPU and 67 % less memory than the proxy it replaced), mobile operating systems (about 21 % of new native code in Android 13), operating-system kernels (Linux kernel Rust support).

Avoid when

  • Prototypes, data science, UI-heavy apps; or Go would meet the latency target and the team can't invest in learning.

Deep dive

Decision table

JobPickWhy
HTTP/gRPC serviceaxum or actix-web + tokio, tonic for gRPCMature, fast, tower middleware
CPU-parallel batch workrayonWork-stealing par_iter with no data races
Serializationserde (+ serde_json, bincode, rkyv for zero-copy)De facto standard
Database accesssqlx (async, compile-checked SQL) or dieselTyped queries
Errorsthiserror in libraries, anyhow in binariesTyped vs convenient
CLIclap deriveHelp/completions for free
Python extensionpyo3 + maturinFast hot loops under Python
Embeddedembassy, embedded-halAsync on microcontrollers
GUITauri (web UI), egui, SlintWeakest area; no dominant native toolkit

Memory and performance model

  • 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

namelangrepoinstallverdictlicencehealthas_ofnotes
rustc + cargoRustrust-lang/rustcurl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh————compiler and std; cargo ships with it
rustupRustrust-lang/rustupbrew install rustup or the sh.rustup.rs script————toolchain manager; rustup component add clippy rustfmt
clippyRustrust-lang/rust-clippyrustup component add clippy————lints
tokioRusttokio-rs/tokiocargo add tokio --features full————async runtime, work-stealing
rayonRustrayon-rs/rayoncargo 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

Alternatives and related