Rust 2024 edition • language to production

Rust Language & Production Ecosystem

A book-like refresher covering the complete language, ownership, traits, unsafe boundaries, threads, async Rust, web/data engineering, tooling, security, and production architecture.

Contents

Part I — Getting Started

Toolchains, Cargo, crates, workspaces, and project organization.

  1. 01Rust’s execution and safety modelRust compiles ahead of time to native code and enforces ownership, borrowing, lifetimes, and thread-safety rules at compile time. It has deterministic cleanup and no mandatory garbage collector or async runtime.
  2. 02Install Rust with rustuprustup manages stable, beta, and nightly toolchains, components, documentation, and cross-compilation targets. Cargo is Rust’s package manager, build system, test runner, and documentation driver.
  3. 03Packages, crates, and targetsA package is described by Cargo.toml. A crate is a compilation unit. A package may contain one library crate plus multiple binary, example, test, and benchmark targets.
  4. 04Cargo workspacesA workspace coordinates related packages with a shared Cargo.lock and target directory. Use workspace dependencies and lints to keep versions and policy consistent.
  5. 05Project layout and dependency directionStart with one crate. Split when boundaries are real: binaries should compose infrastructure, while library crates hold reusable domain and application logic. Avoid a premature crate per folder.

Part II — Language Fundamentals

Bindings, types, expressions, control flow, structs, enums, methods, and modules.

  1. 01Bindings, mutability, shadowing, and constantsBindings are immutable by default. mut permits mutation; shadowing creates a new binding and can change its type. const requires an explicit type and compile-time expression.
  2. 02Scalar and compound typesScalar types include signed and unsigned integers, floats, bool, and Unicode char. Compound types include tuples and fixed-size arrays. usize and isize match pointer width.
  3. 03Statements, expressions, and functionsMost Rust constructs are expressions. A block returns its final expression without a semicolon. Function parameters and return values require explicit types.
  4. 04Control flow and pattern matchingif, match, loop, while, and for express control flow. match is exhaustive; patterns destructure enums, structs, tuples, references, and ranges. if let and let else handle focused cases.
  5. 05Structs, enums, and methodsStructs model product types; enums model one-of-many variants and may carry data. impl blocks define methods and associated functions. Option and Result are standard enums rather than special null/exception syntax.
  6. 06Modules, paths, and visibilityModules form a tree within a crate. Items are private by default. pub, pub(crate), pub(super), and pub(in path) control visibility. use imports names; pub use deliberately re-exports an API.

Part III — Ownership and Memory

Moves, borrowing, slices, lifetimes, smart pointers, RAII, pinning, layout, and unsafe.

  1. 01Ownership, moves, and CopyEvery value has an owner. Assigning or passing a non-Copy value normally moves it, making the old binding unusable. Copy types are duplicated bitwise. Clone is an explicit potentially expensive duplication.
  2. 02Borrowing and references&T permits shared reading and &mut T permits exclusive mutation. Rust prevents aliasing mutable state. Reborrowing often lets a mutable reference be used temporarily without transferring it permanently.
  3. 03Slices, String, and strA slice is a borrowed view into contiguous data. String owns growable UTF-8 bytes; str is a UTF-8 string slice. Integer indexing is forbidden because Unicode scalar values have variable byte lengths.
  4. 04Lifetimes and elisionLifetimes describe relationships among references. They do not extend values’ lives. Most are inferred through elision rules; explicit parameters are needed when multiple borrowed inputs and outputs create ambiguity.
  5. 05Drop and RAIIDrop runs when a value leaves scope, enabling deterministic cleanup of memory, files, locks, sockets, and other resources. Mutex guards unlock on drop; manually calling Drop::drop is forbidden, but std::mem::drop ends ownership early.
  6. 06Box, Rc, Arc, Cell, and RefCellBox owns heap data. Rc provides non-thread-safe shared ownership; Arc provides atomic shared ownership. Cell and RefCell allow interior mutability, with RefCell enforcing borrow rules at runtime.
  7. 07Pin, Unpin, layout, and unsafePin prevents moving a value through a pinned pointer and matters for address-sensitive values and many futures. Default struct layout is not a stable ABI; repr(C) and repr(transparent) provide explicit layout guarantees. unsafe permits narrowly scoped unchecked operations whose invariants must be documented.

Part IV — Traits, Generics, and Abstraction

Traits, bounds, associated types, dispatch, iterators, closures, and conversions.

  1. 01Traits and implementationsTraits define behavior and are implemented explicitly. Default methods reuse behavior. Coherence and orphan rules prevent conflicting implementations across crates.
  2. 02Generics and monomorphizationGeneric functions and types express reusable algorithms. Static generic code is commonly monomorphized into concrete machine code, preserving zero-cost abstraction at the expense of potential code size.
  3. 03Trait bounds and where clausesBounds state capabilities a generic type must provide. where clauses make complex relationships readable. Higher-ranked trait bounds express behavior valid for any suitable lifetime.
  4. 04Associated types, GATs, and const genericsAssociated types let an implementation select related types. Generic associated types parameterize associated types. Const generics parameterize code by compile-time values such as array lengths.
  5. 05Static and dynamic dispatchGenerics and impl Trait use static dispatch in common cases. dyn Trait uses runtime vtable dispatch and enables heterogeneous collections. Object safety and lifetime bounds determine whether a trait can be used dynamically.
  6. 06Closures and Fn traitsClosures infer parameter and return types and capture their environment by shared borrow, mutable borrow, or move. Their behavior determines whether they implement Fn, FnMut, or FnOnce.
  7. 07Iterator and IntoIteratorIterator is lazy and driven by next. Adapter chains do no work until consumed. IntoIterator controls for-loop behavior and whether values are borrowed, mutably borrowed, or moved.
  8. 08Conversion traits and DerefFrom and Into model infallible conversion; TryFrom and TryInto model fallible conversion. AsRef provides a cheap borrowed view. Deref supports pointer-like ergonomics but should not be used merely to simulate inheritance.

Part V — Errors and Metaprogramming

Result, Option, application errors, panic behavior, macros, derives, and attributes.

  1. 01Result, Option, and ?Result represents success or failure; Option represents presence or absence. The ? operator returns early and converts compatible error types through From. Combinators help when they improve clarity rather than hide control flow.
  2. 02thiserror and anyhowLibraries should expose structured inspectable errors; thiserror derives those types. Applications often use anyhow for ergonomic propagation and context. Preserve the source chain and translate errors only at meaningful boundaries.
  3. 03panic, unwind, and abortpanic reports violated assumptions or unrecoverable states. It may unwind or abort based on the profile. Avoid panic, unwrap, and expect in normal request/input failure paths unless the invariant truly cannot be violated.
  4. 04Declarative macrosmacro_rules! matches token patterns and expands syntax. It can express repeated or variadic syntax unavailable to normal functions, but complex macro error messages and hygiene deserve careful API design.
  5. 05Procedural macros and derivesProcedural macros transform token streams. Derive macros generate implementations; attribute and function-like macros transform syntax. serde, clap, thiserror, async frameworks, and ORMs rely heavily on them.
  6. 06Attributes, cfg, and feature flagsAttributes configure the compiler and tools. cfg includes code conditionally by target or feature. Cargo features are additive and should represent optional capabilities rather than mutually exclusive modes.

Part VI — Threads and Parallelism

Threads, scoped threads, channels, shared state, Send/Sync, atomics, Rayon, and processes.

  1. 01OS threads and JoinHandlestd::thread creates OS threads. move transfers captured ownership. Joining waits for completion and surfaces panics. Thread creation is heavier than async tasks, so use pools for many short jobs.
  2. 02Scoped threadsthread::scope permits child threads to borrow stack data because all scoped threads are guaranteed to finish before the scope returns.
  3. 03Channels and backpressurestd::sync::mpsc provides multi-producer single-consumer channels. sync_channel is bounded and forces producers to wait when full. Crossbeam-channel and flume offer richer channel selection and MPMC behavior.
  4. 04Mutex, RwLock, Condvar, OnceLock, and LazyLockMutex and RwLock protect shared state with guards that unlock on drop. Poisoning signals a panic while locked. Condvar coordinates state changes; OnceLock and LazyLock implement one-time initialization.
  5. 05Send and SyncSend means ownership may cross thread boundaries. Sync means &T may be shared across threads. These auto traits are derived from fields. Rc and RefCell are not thread-safe; Arc and locking/atomic types can be.
  6. 06Atomics and memory orderingAtomic types perform indivisible operations. Relaxed is suitable for independent counters; Acquire/Release and SeqCst impose stronger ordering. Lock-free code needs a precise invariant and should not be chosen simply to avoid a mutex.
  7. 07Rayon data parallelismRayon adds work-stealing parallel iterators for independent CPU-bound work. It is usually a better fit than manually spawning a thread per item and is distinct from async network concurrency.
  8. 08Processes and IPCstd::process launches independent OS processes. Processes provide isolation, privilege boundaries, and independent scaling, but require serialization and IPC through pipes, sockets, files, or network protocols.

Part VII — Async Rust

Future polling, Tokio, tasks, cancellation, channels, blocking work, streams, and structured concurrency.

  1. 01Future, async, await, and pollingAn async function returns a Future. Futures are lazy and make progress only when polled by an executor. await suspends the current task when pending rather than blocking an OS thread.
  2. 02Tokio runtime and tasksTokio is the dominant general-purpose async runtime for network services. tokio::spawn starts a lightweight task; on the multithreaded runtime, spawned futures must generally be Send and 'static.
  3. 03join, select, timeouts, and cancellationjoin runs futures concurrently and waits for all. select races branches and drops unselected futures, so cancellation safety matters. Use timeout/deadline wrappers and propagate cancellation deliberately.
  4. 04Async channels and synchronizationTokio supplies bounded mpsc, oneshot, broadcast, watch, Mutex, RwLock, Semaphore, and Notify. Prefer bounded channels. Avoid holding any guard across await unless the lock and invariant are designed for it.
  5. 05Blocking work in async applicationsBlocking a runtime worker stalls unrelated tasks. Use spawn_blocking for blocking calls or moderate CPU work, Rayon/dedicated pools for sustained CPU workloads, and true async APIs for network I/O.
  6. 06Streams, pinning, and async traitsStream is the asynchronous counterpart to Iterator. Pin often appears because futures/streams may be address-sensitive. Native async trait methods support many static-dispatch uses; boxed futures or async-trait remain useful for some dyn interfaces.
  7. 07Structured concurrency and task ownershipEvery task needs an owner, cancellation path, and observed failure. JoinSet manages dynamic Tokio tasks; CancellationToken coordinates shutdown. Dropping a JoinHandle detaches rather than automatically cancels a task.

Part VIII — Standard Library and Data

Collections, text, I/O, serialization, time, parsing, cryptography, CLI, and processes.

  1. 01CollectionsVec, VecDeque, HashMap, HashSet, BTreeMap, BTreeSet, BinaryHeap, and LinkedList cover common data structures. Select by ordering, lookup, mutation, and memory behavior rather than familiarity alone.
  2. 02Strings, bytes, Unicode, and formattingString and str are UTF-8. len returns bytes, not characters. Iterate bytes, chars, or grapheme clusters depending on the requirement. format and Display/Debug traits control textual representation.
  3. 03Files, paths, and streaming I/ORead and Write traits compose streaming operations. BufReader and BufWriter reduce syscall overhead. Path and PathBuf support platform-native paths, which are not guaranteed to be UTF-8.
  4. 04Serde and data formatsSerde separates the serialization data model from formats. Combine derive-based Serialize/Deserialize with serde_json, toml, YAML, postcard, bincode, CSV, or other format crates. Validate domain invariants after decoding.
  5. 05Time, UUID, URL, regex, and parsingstd::time covers durations, monotonic Instant, and SystemTime. time or chrono handles civil dates and zones. uuid, url, regex, and nom are common focused crates for identifiers and parsing.
  6. 06Randomness, hashing, and cryptographyUse rand for ordinary randomness and OS-backed cryptographic randomness where required. Use RustCrypto or audited protocol libraries. Passwords require Argon2, scrypt, or bcrypt, not a fast general-purpose hash.
  7. 07CLI and environmentstd::env handles raw arguments and variables; clap derives polished parsers, validation, help, shell completion, and environment integration. Keep parsing separate from application logic.

Part IX — Web, APIs, Data, and Frontends

HTTP frameworks, middleware, clients, auth, SQL, queues, RPC, templates, bundling, and Wasm.

  1. 01HTTP framework decision guideAxum integrates Tokio, Tower, and Hyper and is a strong default for async services. Actix Web is mature and fast. Rocket emphasizes ergonomics. Warp uses filter composition. Hyper is the low-level HTTP foundation.
  2. 02Axum extractors, responses, and stateAxum handlers are async functions whose arguments implement extraction traits and whose return values implement IntoResponse. Shared application state should be cheap to clone, commonly wrapping pools and services in Arc.
  3. 03Tower middlewareTower models composable services and layers. tower-http supplies tracing, request IDs, CORS, compression, body limits, timeouts, sensitive-header handling, and static-file utilities.
  4. 04HTTP clients with reqwestreqwest is the common high-level async and blocking HTTP client. Reuse Client instances for connection pooling, set deadlines, validate response status, bound response sizes, and retry only safe/idempotent operations.
  5. 05Authentication and authorizationUse maintained identity providers and protocol libraries. Browser apps often use secure server sessions. OAuth 2.0 delegates access; OpenID Connect adds identity. JWT is a token format, not an architecture. Authorization must be enforced near resources/use cases.
  6. 06SQLx, Diesel, and SeaORMSQLx offers async SQL and optional compile-time query checking without imposing an ORM. Diesel provides a typed query DSL. SeaORM is async and entity-oriented. Choose from team SQL skill, query complexity, and desired abstraction.
  7. 07Transactions, pools, and migrationsDatabase pools are shared and bounded. Transactions tie work to one connection and roll back on drop unless committed. Keep them short. Use sqlx-cli, Diesel migrations, refinery, or SeaORM migration with a deliberate deployment strategy.
  8. 08Redis, caching, queues, and eventsredis and fred are common clients. Caching requires TTL, invalidation, key boundaries, and stampede control. Durable jobs/events need bounded retries, idempotent consumers, dead-letter handling, schemas, and observability.
  9. 09gRPC, GraphQL, WebSocket, and SSETonic plus Prost is the prominent Tokio gRPC stack. async-graphql and Juniper cover GraphQL. WebSockets provide full duplex messaging; SSE is simpler for one-way browser event streams over HTTP.
  10. 10Templates, frontend bundling, and WebAssemblyServe a separately built Vite app, embed dist assets with rust-embed/include_dir, or render HTML with Askama/Maud. Rust-to-Wasm uses wasm-bindgen and frameworks such as Leptos, Yew, or Dioxus when the trade-off is deliberate.

Part X — Testing and Tooling

Tests, properties, mocks, benchmarks, formatting, linting, dependencies, builds, publishing, and documentation.

  1. 01Unit, integration, and documentation testsUnit tests commonly live beside source under cfg(test). Files under tests are separate integration-test crates. Rustdoc examples can compile and run as documentation tests.
  2. 02Async tests and test fixturestokio::test runs async tests. rstest supports fixtures and parameterized cases. Testcontainers can launch real databases and brokers; wiremock/httpmock emulate HTTP boundaries.
  3. 03Property and snapshot testingproptest and quickcheck generate inputs and shrink failures, making them valuable for parsers, codecs, and invariants. insta manages reviewed snapshots for stable complex output.
  4. 04Mocks, fakes, and boundariesPrefer small traits at real boundaries and deterministic fakes with useful behavior. mockall generates interaction mocks when call verification matters. Excessive mocks couple tests to implementation sequences.
  5. 05Benchmarks and profilingCriterion provides statistically informed benchmarks. cargo-flamegraph and platform profilers reveal hot paths; heaptrack, DHAT, Valgrind, and Tokio Console help with allocation and async diagnostics.
  6. 06rustfmt, Clippy, rust-analyzer, and CIrustfmt standardizes layout; Clippy catches correctness and idiom issues; rust-analyzer powers editor analysis. Run formatting, linting, tests, and docs across the workspace and selected feature combinations.
  7. 07Features and dependency managementCargo features are additive compile-time capabilities. Inspect duplicate versions and feature activation with cargo tree. Commit Cargo.lock for applications. Use cargo update deliberately and define an MSRV policy.
  8. 08Cross-compilation, build.rs, and FFIrustup adds targets; cross and cargo-zigbuild simplify builds. build.rs performs native discovery/generation before compilation. bindgen and cbindgen bridge C APIs. Keep unsafe FFI wrappers small and expose safe APIs.
  9. 09Editions, semver, MSRV, and publishingEditions evolve syntax and idioms without splitting library compatibility. rust-version declares MSRV. crates.io publication requires package metadata and semantic-version discipline; preflight with cargo package and publish --dry-run.
  10. 10Documentation and API designUse rustdoc comments with examples, links, panics/errors/safety sections, and docs.rs metadata. Public APIs should minimize unnecessary generic parameters, expose stable semantics, and use non_exhaustive where future variants/fields are likely.

Part XI — Production Engineering

Configuration, observability, lifecycle, resilience, security, deployment, performance, and coherent stacks.

  1. 01Configuration, secrets, and startup validationDeserialize configuration once at startup and validate it before serving traffic. config and figment merge sources; clap handles flags; secrecy reduces accidental secret exposure. Avoid hidden global mutable configuration.
  2. 02tracing, metrics, and OpenTelemetrytracing records structured spans and events across sync and async code. tracing-subscriber filters and exports them. Prometheus/metrics crates expose aggregates; OpenTelemetry connects distributed traces and metrics.
  3. 03Graceful shutdown and task lifecycleStop accepting traffic, propagate cancellation, finish bounded in-flight work, stop producers, drain safe queues, and flush telemetry before a deadline. Readiness should turn false before termination in orchestrated systems.
  4. 04Timeouts, retries, bulkheads, and backpressureEvery remote operation needs a deadline. Retry only idempotent/safe work with bounded exponential backoff and jitter. Bound queues and concurrency, shed overload, and align resource pools with downstream capacity.
  5. 05Security and supply chainUse cargo-audit for RustSec advisories, cargo-deny for licenses/bans/sources, cargo-vet for review trust, and cargo-geiger to inventory unsafe code. Review proc macros and build scripts because they execute during builds.
  6. 06Containers and release profilesUse multi-stage builds, locked dependencies, non-root runtime users, necessary CA/timezone files, and read-only filesystems where practical. Measure LTO, codegen units, stripping, and static-linking trade-offs.
  7. 07Performance mental modelMeasure algorithms, allocations, cloning, cache behavior, syscalls, serialization, contention, and async wakeups. Borrow and batch when they simplify ownership and improve measured behavior; do not clone merely to silence the compiler.
  8. 08Common Rust pitfallsWatch for unnecessary clone, Arc<Mutex<_>> as a default architecture, blocking runtime workers, holding guards across await, detached tasks, unbounded channels, careless unwrap, feature explosion, and undocumented unsafe invariants.
  9. 09Coherent production stack choicesA strong async API default is Tokio + Axum + Tower + Serde + tracing + SQLx. Actix Web is a sound alternative for its ecosystem/team familiarity; Diesel for typed query composition; Tonic for gRPC; Leptos/Yew/Dioxus only when Rust frontend benefits justify complexity.
Part I — Getting Started

Rust’s execution and safety model

Rust compiles ahead of time to native code and enforces ownership, borrowing, lifetimes, and thread-safety rules at compile time. It has deterministic cleanup and no mandatory garbage collector or async runtime.

Rust compiles ahead of time to native code and enforces ownership, borrowing, lifetimes, and thread-safety rules at compile time. It has deterministic cleanup and no mandatory garbage collector or async runtime.

fn main() {
    let message = String::from("safe, native, explicit");
    println!("{message}");
}
overviewnativememory-safety
Part I — Getting Started

Install Rust with rustup

rustup manages stable, beta, and nightly toolchains, components, documentation, and cross-compilation targets. Cargo is Rust’s package manager, build system, test runner, and documentation driver.

rustup manages stable, beta, and nightly toolchains, components, documentation, and cross-compilation targets. Cargo is Rust’s package manager, build system, test runner, and documentation driver.

rustup update stable
rustup component add rustfmt clippy
rustc --version
cargo --version
cargo new inventory
cd inventory
cargo run
rustupcargotoolchain
Part I — Getting Started

Packages, crates, and targets

A package is described by Cargo.toml. A crate is a compilation unit. A package may contain one library crate plus multiple binary, example, test, and benchmark targets.

A package is described by Cargo.toml. A crate is a compilation unit. A package may contain one library crate plus multiple binary, example, test, and benchmark targets.

cargo check
cargo build --release
cargo test
cargo doc --open

# Conventional targets:
# src/lib.rs
# src/main.rs
# src/bin/worker.rs
# examples/client.rs
packagecratetarget
Part I — Getting Started

Cargo workspaces

A workspace coordinates related packages with a shared Cargo.lock and target directory. Use workspace dependencies and lints to keep versions and policy consistent.

A workspace coordinates related packages with a shared Cargo.lock and target directory. Use workspace dependencies and lints to keep versions and policy consistent.

[workspace]
members = ["crates/api", "crates/domain", "crates/storage"]
resolver = "3"

[workspace.package]
edition = "2024"

[workspace.dependencies]
serde = { version = "1", features = ["derive"] }
workspaceCargo.toml
Part I — Getting Started

Project layout and dependency direction

Start with one crate. Split when boundaries are real: binaries should compose infrastructure, while library crates hold reusable domain and application logic. Avoid a premature crate per folder.

Start with one crate. Split when boundaries are real: binaries should compose infrastructure, while library crates hold reusable domain and application logic. Avoid a premature crate per folder.

app/
├── Cargo.toml
├── Cargo.lock
├── src/
│   ├── main.rs
│   ├── lib.rs
│   ├── config.rs
│   └── http/
├── tests/
└── migrations/
layoutarchitecture
Part II — Language Fundamentals

Bindings, mutability, shadowing, and constants

Bindings are immutable by default. mut permits mutation; shadowing creates a new binding and can change its type. const requires an explicit type and compile-time expression.

Bindings are immutable by default. mut permits mutation; shadowing creates a new binding and can change its type. const requires an explicit type and compile-time expression.

const MAX_RETRIES: u32 = 3;
let port = "8080";
let port: u16 = port.parse()?;
let mut attempts = 0;
attempts += 1;
letmutshadowingconst
Part II — Language Fundamentals

Scalar and compound types

Scalar types include signed and unsigned integers, floats, bool, and Unicode char. Compound types include tuples and fixed-size arrays. usize and isize match pointer width.

Scalar types include signed and unsigned integers, floats, bool, and Unicode char. Compound types include tuples and fixed-size arrays. usize and isize match pointer width.

let count: u64 = 42;
let ratio: f64 = 0.75;
let symbol: char = '🦀';
let point: (i32, i32) = (4, 9);
let bytes: [u8; 4] = [1, 2, 3, 4];
typestuplearray
Part II — Language Fundamentals

Statements, expressions, and functions

Most Rust constructs are expressions. A block returns its final expression without a semicolon. Function parameters and return values require explicit types.

Most Rust constructs are expressions. A block returns its final expression without a semicolon. Function parameters and return values require explicit types.

fn doubled_plus_one(n: i32) -> i32 {
    let doubled = {
        let x = n * 2;
        x
    };
    doubled + 1
}
expressionstatementfunction
Part II — Language Fundamentals

Control flow and pattern matching

if, match, loop, while, and for express control flow. match is exhaustive; patterns destructure enums, structs, tuples, references, and ranges. if let and let else handle focused cases.

if, match, loop, while, and for express control flow. match is exhaustive; patterns destructure enums, structs, tuples, references, and ranges. if let and let else handle focused cases.

match event {
    Event::Connected { user_id } => println!("{user_id}"),
    Event::Message(text) if text.is_empty() => println!("empty"),
    Event::Message(text) => println!("{text}"),
    Event::Closed => println!("closed"),
}
matchpatternloopif-let
Part II — Language Fundamentals

Structs, enums, and methods

Structs model product types; enums model one-of-many variants and may carry data. impl blocks define methods and associated functions. Option and Result are standard enums rather than special null/exception syntax.

Structs model product types; enums model one-of-many variants and may carry data. impl blocks define methods and associated functions. Option and Result are standard enums rather than special null/exception syntax.

struct Counter { value: u64 }

impl Counter {
    fn new() -> Self { Self { value: 0 } }
    fn increment(&mut self) { self.value += 1; }
}

enum Command { Create(String), Delete(u64), Quit }
structenumimplmethod
Part II — Language Fundamentals

Modules, paths, and visibility

Modules form a tree within a crate. Items are private by default. pub, pub(crate), pub(super), and pub(in path) control visibility. use imports names; pub use deliberately re-exports an API.

Modules form a tree within a crate. Items are private by default. pub, pub(crate), pub(super), and pub(in path) control visibility. use imports names; pub use deliberately re-exports an API.

// src/lib.rs
pub mod service;
mod storage;
pub use service::OrderService;

// src/service.rs
pub struct OrderService {
    pub(crate) name: String,
}
modulevisibilitypubuse
Part III — Ownership and Memory

Ownership, moves, and Copy

Every value has an owner. Assigning or passing a non-Copy value normally moves it, making the old binding unusable. Copy types are duplicated bitwise. Clone is an explicit potentially expensive duplication.

Every value has an owner. Assigning or passing a non-Copy value normally moves it, making the old binding unusable. Copy types are duplicated bitwise. Clone is an explicit potentially expensive duplication.

let first = String::from("hello");
let second = first;
// println!("{first}"); // moved
println!("{second}");

let a = 5u32;
let b = a; // Copy
println!("{a} {b}");
ownershipmoveCopyClone
Part III — Ownership and Memory

Borrowing and references

&T permits shared reading and &mut T permits exclusive mutation. Rust prevents aliasing mutable state. Reborrowing often lets a mutable reference be used temporarily without transferring it permanently.

&T permits shared reading and &mut T permits exclusive mutation. Rust prevents aliasing mutable state. Reborrowing often lets a mutable reference be used temporarily without transferring it permanently.

fn length(value: &str) -> usize { value.len() }
fn append_world(value: &mut String) { value.push_str(" world"); }

let mut text = String::from("hello");
println!("{}", length(&text));
append_world(&mut text);
borrowreferencemutable
Part III — Ownership and Memory

Slices, String, and str

A slice is a borrowed view into contiguous data. String owns growable UTF-8 bytes; str is a UTF-8 string slice. Integer indexing is forbidden because Unicode scalar values have variable byte lengths.

A slice is a borrowed view into contiguous data. String owns growable UTF-8 bytes; str is a UTF-8 string slice. Integer indexing is forbidden because Unicode scalar values have variable byte lengths.

fn first_word(input: &str) -> &str {
    input.split_whitespace().next().unwrap_or("")
}

let text = String::from("hello rust");
let word = first_word(&text);
let bytes: &[u8] = &text.as_bytes()[0..5];
sliceStringstrUTF-8
Part III — Ownership and Memory

Lifetimes and elision

Lifetimes describe relationships among references. They do not extend values’ lives. Most are inferred through elision rules; explicit parameters are needed when multiple borrowed inputs and outputs create ambiguity.

Lifetimes describe relationships among references. They do not extend values’ lives. Most are inferred through elision rules; explicit parameters are needed when multiple borrowed inputs and outputs create ambiguity.

fn longest<'a>(left: &'a str, right: &'a str) -> &'a str {
    if left.len() >= right.len() { left } else { right }
}

struct View<'a> {
    title: &'a str,
}
lifetimeelisionborrow-checker
Part III — Ownership and Memory

Drop and RAII

Drop runs when a value leaves scope, enabling deterministic cleanup of memory, files, locks, sockets, and other resources. Mutex guards unlock on drop; manually calling Drop::drop is forbidden, but std::mem::drop ends ownership early.

Drop runs when a value leaves scope, enabling deterministic cleanup of memory, files, locks, sockets, and other resources. Mutex guards unlock on drop; manually calling Drop::drop is forbidden, but std::mem::drop ends ownership early.

struct Connection(u64);

impl Drop for Connection {
    fn drop(&mut self) {
        println!("closing {}", self.0);
    }
}

let connection = Connection(7);
drop(connection);
DropRAIIcleanup
Part III — Ownership and Memory

Box, Rc, Arc, Cell, and RefCell

Box owns heap data. Rc provides non-thread-safe shared ownership; Arc provides atomic shared ownership. Cell and RefCell allow interior mutability, with RefCell enforcing borrow rules at runtime.

Box owns heap data. Rc provides non-thread-safe shared ownership; Arc provides atomic shared ownership. Cell and RefCell allow interior mutability, with RefCell enforcing borrow rules at runtime.

use std::{cell::RefCell, rc::Rc, sync::Arc};

let boxed = Box::new([0u8; 1024]);
let shared = Rc::new(RefCell::new(vec![1, 2]));
shared.borrow_mut().push(3);

let thread_safe = Arc::new(String::from("shared"));
BoxRcArcCellRefCell
Part III — Ownership and Memory

Pin, Unpin, layout, and unsafe

Pin prevents moving a value through a pinned pointer and matters for address-sensitive values and many futures. Default struct layout is not a stable ABI; repr(C) and repr(transparent) provide explicit layout guarantees. unsafe permits narrowly scoped unchecked operations whose invariants must be documented.

Pin prevents moving a value through a pinned pointer and matters for address-sensitive values and many futures. Default struct layout is not a stable ABI; repr(C) and repr(transparent) provide explicit layout guarantees. unsafe permits narrowly scoped unchecked operations whose invariants must be documented.

#[repr(C)]
struct Header {
    kind: u16,
    length: u32,
}

unsafe fn read_u32(ptr: *const u32) -> u32 {
    // Safety: valid, aligned, initialized, live pointer.
    unsafe { ptr.read() }
}
PinUnpinreprunsafeFFI
Part IV — Traits, Generics, and Abstraction

Traits and implementations

Traits define behavior and are implemented explicitly. Default methods reuse behavior. Coherence and orphan rules prevent conflicting implementations across crates.

Traits define behavior and are implemented explicitly. Default methods reuse behavior. Coherence and orphan rules prevent conflicting implementations across crates.

trait Summary {
    fn title(&self) -> &str;
    fn summarize(&self) -> String {
        format!("Summary: {}", self.title())
    }
}

impl Summary for Article {
    fn title(&self) -> &str { &self.title }
}
traitimplcoherence
Part IV — Traits, Generics, and Abstraction

Generics and monomorphization

Generic functions and types express reusable algorithms. Static generic code is commonly monomorphized into concrete machine code, preserving zero-cost abstraction at the expense of potential code size.

Generic functions and types express reusable algorithms. Static generic code is commonly monomorphized into concrete machine code, preserving zero-cost abstraction at the expense of potential code size.

fn max<T: Ord>(left: T, right: T) -> T {
    if left >= right { left } else { right }
}

struct Pair<T> { left: T, right: T }
genericmonomorphization
Part IV — Traits, Generics, and Abstraction

Trait bounds and where clauses

Bounds state capabilities a generic type must provide. where clauses make complex relationships readable. Higher-ranked trait bounds express behavior valid for any suitable lifetime.

Bounds state capabilities a generic type must provide. where clauses make complex relationships readable. Higher-ranked trait bounds express behavior valid for any suitable lifetime.

fn render<T>(value: &T) -> String
where
    T: std::fmt::Display + Send + Sync,
{
    value.to_string()
}
trait-boundwhereHRTB
Part IV — Traits, Generics, and Abstraction

Associated types, GATs, and const generics

Associated types let an implementation select related types. Generic associated types parameterize associated types. Const generics parameterize code by compile-time values such as array lengths.

Associated types let an implementation select related types. Generic associated types parameterize associated types. Const generics parameterize code by compile-time values such as array lengths.

trait Repository {
    type Item;
    type Error;
    fn get(&self, id: u64) -> Result<Self::Item, Self::Error>;
}

fn first<const N: usize>(values: [u8; N]) -> Option<u8> {
    values.first().copied()
}
associated-typeGATconst-generics
Part IV — Traits, Generics, and Abstraction

Static and dynamic dispatch

Generics and impl Trait use static dispatch in common cases. dyn Trait uses runtime vtable dispatch and enables heterogeneous collections. Object safety and lifetime bounds determine whether a trait can be used dynamically.

Generics and impl Trait use static dispatch in common cases. dyn Trait uses runtime vtable dispatch and enables heterogeneous collections. Object safety and lifetime bounds determine whether a trait can be used dynamically.

fn log_static(value: &impl std::fmt::Display) {
    println!("{value}");
}

fn log_dynamic(value: &dyn std::fmt::Display) {
    println!("{value}");
}

let handlers: Vec<Box<dyn Handler + Send + Sync>> = vec![];
dyn-Traitimpl-Traitdispatch
Part IV — Traits, Generics, and Abstraction

Closures and Fn traits

Closures infer parameter and return types and capture their environment by shared borrow, mutable borrow, or move. Their behavior determines whether they implement Fn, FnMut, or FnOnce.

Closures infer parameter and return types and capture their environment by shared borrow, mutable borrow, or move. Their behavior determines whether they implement Fn, FnMut, or FnOnce.

let factor = 3;
let scale = |x| x * factor;

fn apply<F: Fn(i32) -> i32>(f: F, value: i32) -> i32 {
    f(value)
}

assert_eq!(apply(scale, 4), 12);
closureFnFnMutFnOnce
Part IV — Traits, Generics, and Abstraction

Iterator and IntoIterator

Iterator is lazy and driven by next. Adapter chains do no work until consumed. IntoIterator controls for-loop behavior and whether values are borrowed, mutably borrowed, or moved.

Iterator is lazy and driven by next. Adapter chains do no work until consumed. IntoIterator controls for-loop behavior and whether values are borrowed, mutably borrowed, or moved.

let total: i64 = orders
    .iter()
    .filter(|order| order.active)
    .map(|order| order.amount)
    .sum();

let names: Vec<_> = users.into_iter().map(|u| u.name).collect();
IteratorIntoIteratorcollect
Part IV — Traits, Generics, and Abstraction

Conversion traits and Deref

From and Into model infallible conversion; TryFrom and TryInto model fallible conversion. AsRef provides a cheap borrowed view. Deref supports pointer-like ergonomics but should not be used merely to simulate inheritance.

From and Into model infallible conversion; TryFrom and TryInto model fallible conversion. AsRef provides a cheap borrowed view. Deref supports pointer-like ergonomics but should not be used merely to simulate inheritance.

struct UserId(u64);

impl TryFrom<&str> for UserId {
    type Error = std::num::ParseIntError;
    fn try_from(value: &str) -> Result<Self, Self::Error> {
        value.parse().map(UserId)
    }
}
FromIntoTryFromAsRefDeref
Part V — Errors and Metaprogramming

Result, Option, and ?

Result represents success or failure; Option represents presence or absence. The ? operator returns early and converts compatible error types through From. Combinators help when they improve clarity rather than hide control flow.

Result represents success or failure; Option represents presence or absence. The ? operator returns early and converts compatible error types through From. Combinators help when they improve clarity rather than hide control flow.

fn load_port(path: &Path) -> Result<u16, AppError> {
    let text = std::fs::read_to_string(path)?;
    let port = text.trim().parse::<u16>()?;
    Ok(port)
}

let name = user.nickname.as_deref().unwrap_or("anonymous");
ResultOptionquestion-mark
Part V — Errors and Metaprogramming

thiserror and anyhow

Libraries should expose structured inspectable errors; thiserror derives those types. Applications often use anyhow for ergonomic propagation and context. Preserve the source chain and translate errors only at meaningful boundaries.

Libraries should expose structured inspectable errors; thiserror derives those types. Applications often use anyhow for ergonomic propagation and context. Preserve the source chain and translate errors only at meaningful boundaries.

#[derive(Debug, thiserror::Error)]
enum ConfigError {
    #[error("failed to read {path}")]
    Read {
        path: PathBuf,
        #[source] source: std::io::Error,
    },
    #[error("invalid port: {0}")]
    InvalidPort(#[from] std::num::ParseIntError),
}
thiserroranyhowerror-source
Part V — Errors and Metaprogramming

panic, unwind, and abort

panic reports violated assumptions or unrecoverable states. It may unwind or abort based on the profile. Avoid panic, unwrap, and expect in normal request/input failure paths unless the invariant truly cannot be violated.

panic reports violated assumptions or unrecoverable states. It may unwind or abort based on the profile. Avoid panic, unwrap, and expect in normal request/input failure paths unless the invariant truly cannot be violated.

fn element(values: &[i32], index: usize) -> Option<i32> {
    values.get(index).copied()
}

// Cargo.toml:
// [profile.release]
// panic = "abort"
panicunwindabort
Part V — Errors and Metaprogramming

Declarative macros

macro_rules! matches token patterns and expands syntax. It can express repeated or variadic syntax unavailable to normal functions, but complex macro error messages and hygiene deserve careful API design.

macro_rules! matches token patterns and expands syntax. It can express repeated or variadic syntax unavailable to normal functions, but complex macro error messages and hygiene deserve careful API design.

macro_rules! map {
    ($($key:expr => $value:expr),* $(,)?) => {{
        let mut result = std::collections::HashMap::new();
        $(result.insert($key, $value);)*
        result
    }};
}

let headers = map! { "accept" => "*/*" };
macro_rulesmacrohygiene
Part V — Errors and Metaprogramming

Procedural macros and derives

Procedural macros transform token streams. Derive macros generate implementations; attribute and function-like macros transform syntax. serde, clap, thiserror, async frameworks, and ORMs rely heavily on them.

Procedural macros transform token streams. Derive macros generate implementations; attribute and function-like macros transform syntax. serde, clap, thiserror, async frameworks, and ORMs rely heavily on them.

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct User {
    user_id: u64,
    display_name: String,
}
proc-macroderiveserde
Part V — Errors and Metaprogramming

Attributes, cfg, and feature flags

Attributes configure the compiler and tools. cfg includes code conditionally by target or feature. Cargo features are additive and should represent optional capabilities rather than mutually exclusive modes.

Attributes configure the compiler and tools. cfg includes code conditionally by target or feature. Cargo features are additive and should represent optional capabilities rather than mutually exclusive modes.

#[cfg(target_os = "windows")]
fn platform_name() -> &'static str { "windows" }

#[cfg(feature = "telemetry")]
mod telemetry;

#[derive(Debug)]
#[non_exhaustive]
pub enum Mode { Development, Production }
attributecfgfeature
Part VI — Threads and Parallelism

OS threads and JoinHandle

std::thread creates OS threads. move transfers captured ownership. Joining waits for completion and surfaces panics. Thread creation is heavier than async tasks, so use pools for many short jobs.

std::thread creates OS threads. move transfers captured ownership. Joining waits for completion and surfaces panics. Thread creation is heavier than async tasks, so use pools for many short jobs.

let values = vec![1, 2, 3];
let handle = std::thread::spawn(move || {
    values.into_iter().sum::<i32>()
});
let total = handle.join().expect("worker panicked");
threadJoinHandlemove
Part VI — Threads and Parallelism

Scoped threads

thread::scope permits child threads to borrow stack data because all scoped threads are guaranteed to finish before the scope returns.

thread::scope permits child threads to borrow stack data because all scoped threads are guaranteed to finish before the scope returns.

let values = vec![1, 2, 3, 4];

std::thread::scope(|scope| {
    let left = scope.spawn(|| values[..2].iter().sum::<i32>());
    let right = scope.spawn(|| values[2..].iter().sum::<i32>());
    println!("{}", left.join().unwrap() + right.join().unwrap());
});
scoped-threadborrow
Part VI — Threads and Parallelism

Channels and backpressure

std::sync::mpsc provides multi-producer single-consumer channels. sync_channel is bounded and forces producers to wait when full. Crossbeam-channel and flume offer richer channel selection and MPMC behavior.

std::sync::mpsc provides multi-producer single-consumer channels. sync_channel is bounded and forces producers to wait when full. Crossbeam-channel and flume offer richer channel selection and MPMC behavior.

let (tx, rx) = std::sync::mpsc::sync_channel(32);

for worker in 0..4 {
    let tx = tx.clone();
    std::thread::spawn(move || tx.send(worker).unwrap());
}
drop(tx);

for result in rx {
    println!("{result}");
}
channelmpsccrossbeamflumebackpressure
Part VI — Threads and Parallelism

Mutex, RwLock, Condvar, OnceLock, and LazyLock

Mutex and RwLock protect shared state with guards that unlock on drop. Poisoning signals a panic while locked. Condvar coordinates state changes; OnceLock and LazyLock implement one-time initialization.

Mutex and RwLock protect shared state with guards that unlock on drop. Poisoning signals a panic while locked. Condvar coordinates state changes; OnceLock and LazyLock implement one-time initialization.

use std::sync::{Arc, Mutex, OnceLock};

static CONFIG: OnceLock<Config> = OnceLock::new();
let counter = Arc::new(Mutex::new(0u64));
let copy = Arc::clone(&counter);

std::thread::spawn(move || *copy.lock().unwrap() += 1)
    .join().unwrap();
MutexRwLockCondvarOnceLock
Part VI — Threads and Parallelism

Send and Sync

Send means ownership may cross thread boundaries. Sync means &T may be shared across threads. These auto traits are derived from fields. Rc and RefCell are not thread-safe; Arc and locking/atomic types can be.

Send means ownership may cross thread boundaries. Sync means &T may be shared across threads. These auto traits are derived from fields. Rc and RefCell are not thread-safe; Arc and locking/atomic types can be.

fn requires_send<T: Send>(_: T) {}
fn requires_sync<T: Sync>(_: &T) {}

requires_send(String::from("owned"));
requires_sync(&std::sync::Mutex::new(0));
SendSyncauto-trait
Part VI — Threads and Parallelism

Atomics and memory ordering

Atomic types perform indivisible operations. Relaxed is suitable for independent counters; Acquire/Release and SeqCst impose stronger ordering. Lock-free code needs a precise invariant and should not be chosen simply to avoid a mutex.

Atomic types perform indivisible operations. Relaxed is suitable for independent counters; Acquire/Release and SeqCst impose stronger ordering. Lock-free code needs a precise invariant and should not be chosen simply to avoid a mutex.

use std::sync::atomic::{AtomicU64, Ordering};

static REQUESTS: AtomicU64 = AtomicU64::new(0);

REQUESTS.fetch_add(1, Ordering::Relaxed);
let count = REQUESTS.load(Ordering::Relaxed);
atomicOrderinglock-free
Part VI — Threads and Parallelism

Rayon data parallelism

Rayon adds work-stealing parallel iterators for independent CPU-bound work. It is usually a better fit than manually spawning a thread per item and is distinct from async network concurrency.

Rayon adds work-stealing parallel iterators for independent CPU-bound work. It is usually a better fit than manually spawning a thread per item and is distinct from async network concurrency.

use rayon::prelude::*;

let sum: u64 = values
    .par_iter()
    .map(|value| expensive_transform(*value))
    .sum();
RayonparallelCPU
Part VI — Threads and Parallelism

Processes and IPC

std::process launches independent OS processes. Processes provide isolation, privilege boundaries, and independent scaling, but require serialization and IPC through pipes, sockets, files, or network protocols.

std::process launches independent OS processes. Processes provide isolation, privilege boundaries, and independent scaling, but require serialization and IPC through pipes, sockets, files, or network protocols.

let output = std::process::Command::new("worker")
    .arg("--job=42")
    .output()?;

if !output.status.success() {
    anyhow::bail!("worker failed");
}
processIPCCommand
Part VII — Async Rust

Future, async, await, and polling

An async function returns a Future. Futures are lazy and make progress only when polled by an executor. await suspends the current task when pending rather than blocking an OS thread.

An async function returns a Future. Futures are lazy and make progress only when polled by an executor. await suspends the current task when pending rather than blocking an OS thread.

async fn fetch(client: &reqwest::Client, url: &str) -> anyhow::Result<String> {
    let response = client.get(url).send().await?.error_for_status()?;
    Ok(response.text().await?)
}
Futureasyncawaitpoll
Part VII — Async Rust

Tokio runtime and tasks

Tokio is the dominant general-purpose async runtime for network services. tokio::spawn starts a lightweight task; on the multithreaded runtime, spawned futures must generally be Send and 'static.

Tokio is the dominant general-purpose async runtime for network services. tokio::spawn starts a lightweight task; on the multithreaded runtime, spawned futures must generally be Send and 'static.

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let task = tokio::spawn(async {
        tokio::time::sleep(Duration::from_millis(50)).await;
        42
    });
    println!("{}", task.await?);
    Ok(())
}
Tokioruntimespawn
Part VII — Async Rust

join, select, timeouts, and cancellation

join runs futures concurrently and waits for all. select races branches and drops unselected futures, so cancellation safety matters. Use timeout/deadline wrappers and propagate cancellation deliberately.

join runs futures concurrently and waits for all. select races branches and drops unselected futures, so cancellation safety matters. Use timeout/deadline wrappers and propagate cancellation deliberately.

let (user, orders) = tokio::try_join!(
    load_user(&pool, id),
    load_orders(&pool, id),
)?;

tokio::select! {
    result = process() => result?,
    _ = shutdown.cancelled() => return Ok(()),
}
joinselecttimeoutcancellation
Part VII — Async Rust

Async channels and synchronization

Tokio supplies bounded mpsc, oneshot, broadcast, watch, Mutex, RwLock, Semaphore, and Notify. Prefer bounded channels. Avoid holding any guard across await unless the lock and invariant are designed for it.

Tokio supplies bounded mpsc, oneshot, broadcast, watch, Mutex, RwLock, Semaphore, and Notify. Prefer bounded channels. Avoid holding any guard across await unless the lock and invariant are designed for it.

let (tx, mut rx) = tokio::sync::mpsc::channel::<Job>(100);

tokio::spawn(async move {
    while let Some(job) = rx.recv().await {
        process(job).await;
    }
});

tx.send(job).await?;
mpsconeshotbroadcastwatchSemaphore
Part VII — Async Rust

Blocking work in async applications

Blocking a runtime worker stalls unrelated tasks. Use spawn_blocking for blocking calls or moderate CPU work, Rayon/dedicated pools for sustained CPU workloads, and true async APIs for network I/O.

Blocking a runtime worker stalls unrelated tasks. Use spawn_blocking for blocking calls or moderate CPU work, Rayon/dedicated pools for sustained CPU workloads, and true async APIs for network I/O.

let parsed = tokio::task::spawn_blocking(move || {
    parse_large_document(bytes)
}).await??;

let data = tokio::fs::read(path).await?;
spawn_blockingblockingCPU
Part VII — Async Rust

Streams, pinning, and async traits

Stream is the asynchronous counterpart to Iterator. Pin often appears because futures/streams may be address-sensitive. Native async trait methods support many static-dispatch uses; boxed futures or async-trait remain useful for some dyn interfaces.

Stream is the asynchronous counterpart to Iterator. Pin often appears because futures/streams may be address-sensitive. Native async trait methods support many static-dispatch uses; boxed futures or async-trait remain useful for some dyn interfaces.

use futures::{Stream, StreamExt};

async fn consume<S>(mut stream: S)
where
    S: Stream<Item = Event> + Unpin,
{
    while let Some(event) = stream.next().await {
        handle(event).await;
    }
}
StreamPinasync-trait
Part VII — Async Rust

Structured concurrency and task ownership

Every task needs an owner, cancellation path, and observed failure. JoinSet manages dynamic Tokio tasks; CancellationToken coordinates shutdown. Dropping a JoinHandle detaches rather than automatically cancels a task.

Every task needs an owner, cancellation path, and observed failure. JoinSet manages dynamic Tokio tasks; CancellationToken coordinates shutdown. Dropping a JoinHandle detaches rather than automatically cancels a task.

let token = tokio_util::sync::CancellationToken::new();
let mut tasks = tokio::task::JoinSet::new();

for worker in 0..4 {
    let child = token.child_token();
    tasks.spawn(async move { run_worker(worker, child).await });
}

token.cancel();
while let Some(result) = tasks.join_next().await { result??; }
JoinSetCancellationTokentask-leak
Part VIII — Standard Library and Data

Collections

Vec, VecDeque, HashMap, HashSet, BTreeMap, BTreeSet, BinaryHeap, and LinkedList cover common data structures. Select by ordering, lookup, mutation, and memory behavior rather than familiarity alone.

Vec, VecDeque, HashMap, HashSet, BTreeMap, BTreeSet, BinaryHeap, and LinkedList cover common data structures. Select by ordering, lookup, mutation, and memory behavior rather than familiarity alone.

use std::collections::{HashMap, VecDeque};

let mut counts = HashMap::new();
*counts.entry("rust").or_insert(0) += 1;

let mut queue = VecDeque::new();
queue.push_back("job");
assert_eq!(queue.pop_front(), Some("job"));
collectionsVecHashMapVecDeque
Part VIII — Standard Library and Data

Strings, bytes, Unicode, and formatting

String and str are UTF-8. len returns bytes, not characters. Iterate bytes, chars, or grapheme clusters depending on the requirement. format and Display/Debug traits control textual representation.

String and str are UTF-8. len returns bytes, not characters. Iterate bytes, chars, or grapheme clusters depending on the requirement. format and Display/Debug traits control textual representation.

let text = "Rust 🦀";
println!("bytes={} chars={}", text.len(), text.chars().count());

for (index, ch) in text.char_indices() {
    println!("{index}: {ch}");
}
StringstrUnicodeformat
Part VIII — Standard Library and Data

Files, paths, and streaming I/O

Read and Write traits compose streaming operations. BufReader and BufWriter reduce syscall overhead. Path and PathBuf support platform-native paths, which are not guaranteed to be UTF-8.

Read and Write traits compose streaming operations. BufReader and BufWriter reduce syscall overhead. Path and PathBuf support platform-native paths, which are not guaranteed to be UTF-8.

use std::{fs::File, io::{self, BufRead, BufReader}, path::Path};

fn lines(path: &Path) -> io::Result<Vec<String>> {
    let file = File::open(path)?;
    BufReader::new(file).lines().collect()
}
ReadWriteBufReaderPath
Part VIII — Standard Library and Data

Serde and data formats

Serde separates the serialization data model from formats. Combine derive-based Serialize/Deserialize with serde_json, toml, YAML, postcard, bincode, CSV, or other format crates. Validate domain invariants after decoding.

Serde separates the serialization data model from formats. Combine derive-based Serialize/Deserialize with serde_json, toml, YAML, postcard, bincode, CSV, or other format crates. Validate domain invariants after decoding.

#[derive(serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
struct Config {
    service_name: String,
    timeout_ms: u64,
}

let config: Config = serde_json::from_slice(&bytes)?;
serdeJSONTOMLserialization
Part VIII — Standard Library and Data

Time, UUID, URL, regex, and parsing

std::time covers durations, monotonic Instant, and SystemTime. time or chrono handles civil dates and zones. uuid, url, regex, and nom are common focused crates for identifiers and parsing.

std::time covers durations, monotonic Instant, and SystemTime. time or chrono handles civil dates and zones. uuid, url, regex, and nom are common focused crates for identifiers and parsing.

let start = std::time::Instant::now();
do_work();
println!("{:?}", start.elapsed());

let id = uuid::Uuid::new_v4();
let url = url::Url::parse("https://example.com/path?q=rust")?;
let re = regex::Regex::new(r"^[a-z0-9_-]+$")?;
timechronouuidurlregexnom
Part VIII — Standard Library and Data

Randomness, hashing, and cryptography

Use rand for ordinary randomness and OS-backed cryptographic randomness where required. Use RustCrypto or audited protocol libraries. Passwords require Argon2, scrypt, or bcrypt, not a fast general-purpose hash.

Use rand for ordinary randomness and OS-backed cryptographic randomness where required. Use RustCrypto or audited protocol libraries. Passwords require Argon2, scrypt, or bcrypt, not a fast general-purpose hash.

use rand::Rng;
let value = rand::rng().random_range(1..=100);

use sha2::{Digest, Sha256};
let digest = Sha256::digest(b"content");

// Password hashing: argon2 / scrypt / bcrypt.
randcryptoSHAArgon2
Part VIII — Standard Library and Data

CLI and environment

std::env handles raw arguments and variables; clap derives polished parsers, validation, help, shell completion, and environment integration. Keep parsing separate from application logic.

std::env handles raw arguments and variables; clap derives polished parsers, validation, help, shell completion, and environment integration. Keep parsing separate from application logic.

#[derive(clap::Parser)]
struct Args {
    #[arg(long, default_value = "127.0.0.1:8080")]
    addr: std::net::SocketAddr,

    #[arg(long, env = "DATABASE_URL")]
    database_url: String,
}
clapCLIenvironment
Part IX — Web, APIs, Data, and Frontends

HTTP framework decision guide

Axum integrates Tokio, Tower, and Hyper and is a strong default for async services. Actix Web is mature and fast. Rocket emphasizes ergonomics. Warp uses filter composition. Hyper is the low-level HTTP foundation.

Axum integrates Tokio, Tower, and Hyper and is a strong default for async services. Actix Web is mature and fast. Rocket emphasizes ergonomics. Warp uses filter composition. Hyper is the low-level HTTP foundation.

let app = axum::Router::new()
    .route("/users/{id}", axum::routing::get(get_user))
    .route("/users", axum::routing::post(create_user))
    .with_state(state);

let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await?;
axum::serve(listener, app).await?;
AxumActixRocketWarpHyper
Part IX — Web, APIs, Data, and Frontends

Axum extractors, responses, and state

Axum handlers are async functions whose arguments implement extraction traits and whose return values implement IntoResponse. Shared application state should be cheap to clone, commonly wrapping pools and services in Arc.

Axum handlers are async functions whose arguments implement extraction traits and whose return values implement IntoResponse. Shared application state should be cheap to clone, commonly wrapping pools and services in Arc.

async fn get_user(
    axum::extract::State(state): axum::extract::State<AppState>,
    axum::extract::Path(id): axum::extract::Path<u64>,
) -> Result<axum::Json<User>, AppError> {
    Ok(axum::Json(state.users.get(id).await?))
}
AxumextractorIntoResponsestate
Part IX — Web, APIs, Data, and Frontends

Tower middleware

Tower models composable services and layers. tower-http supplies tracing, request IDs, CORS, compression, body limits, timeouts, sensitive-header handling, and static-file utilities.

Tower models composable services and layers. tower-http supplies tracing, request IDs, CORS, compression, body limits, timeouts, sensitive-header handling, and static-file utilities.

let middleware = tower::ServiceBuilder::new()
    .layer(tower_http::trace::TraceLayer::new_for_http())
    .layer(tower_http::compression::CompressionLayer::new());

let app = app.layer(middleware);
Towertower-httpmiddleware
Part IX — Web, APIs, Data, and Frontends

HTTP clients with reqwest

reqwest is the common high-level async and blocking HTTP client. Reuse Client instances for connection pooling, set deadlines, validate response status, bound response sizes, and retry only safe/idempotent operations.

reqwest is the common high-level async and blocking HTTP client. Reuse Client instances for connection pooling, set deadlines, validate response status, bound response sizes, and retry only safe/idempotent operations.

let client = reqwest::Client::builder()
    .timeout(Duration::from_secs(5))
    .user_agent("inventory/1.0")
    .build()?;

let body: ApiResponse = client.get(url)
    .send().await?
    .error_for_status()?
    .json().await?;
reqwestHTTP-clienttimeoutretry
Part IX — Web, APIs, Data, and Frontends

Authentication and authorization

Use maintained identity providers and protocol libraries. Browser apps often use secure server sessions. OAuth 2.0 delegates access; OpenID Connect adds identity. JWT is a token format, not an architecture. Authorization must be enforced near resources/use cases.

Use maintained identity providers and protocol libraries. Browser apps often use secure server sessions. OAuth 2.0 delegates access; OpenID Connect adds identity. JWT is a token format, not an architecture. Authorization must be enforced near resources/use cases.

// Common crates by concrete need:
// tower-sessions
// oauth2
// openidconnect
// jsonwebtoken
// argon2
// secrecy

// Secure cookies: Secure, HttpOnly, scoped Path,
// appropriate SameSite, rotation, and expiration.
authsessionOAuthOIDCJWT
Part IX — Web, APIs, Data, and Frontends

SQLx, Diesel, and SeaORM

SQLx offers async SQL and optional compile-time query checking without imposing an ORM. Diesel provides a typed query DSL. SeaORM is async and entity-oriented. Choose from team SQL skill, query complexity, and desired abstraction.

SQLx offers async SQL and optional compile-time query checking without imposing an ORM. Diesel provides a typed query DSL. SeaORM is async and entity-oriented. Choose from team SQL skill, query complexity, and desired abstraction.

let user = sqlx::query_as!(
    User,
    "SELECT id, email FROM users WHERE id = $1",
    id
)
.fetch_one(&pool)
.await?;
SQLxDieselSeaORMdatabase
Part IX — Web, APIs, Data, and Frontends

Transactions, pools, and migrations

Database pools are shared and bounded. Transactions tie work to one connection and roll back on drop unless committed. Keep them short. Use sqlx-cli, Diesel migrations, refinery, or SeaORM migration with a deliberate deployment strategy.

Database pools are shared and bounded. Transactions tie work to one connection and roll back on drop unless committed. Keep them short. Use sqlx-cli, Diesel migrations, refinery, or SeaORM migration with a deliberate deployment strategy.

let mut tx = pool.begin().await?;
sqlx::query!("UPDATE accounts SET balance = balance - $1 WHERE id = $2", amount, from)
    .execute(&mut *tx).await?;
sqlx::query!("UPDATE accounts SET balance = balance + $1 WHERE id = $2", amount, to)
    .execute(&mut *tx).await?;
tx.commit().await?;
transactionpoolmigration
Part IX — Web, APIs, Data, and Frontends

Redis, caching, queues, and events

redis and fred are common clients. Caching requires TTL, invalidation, key boundaries, and stampede control. Durable jobs/events need bounded retries, idempotent consumers, dead-letter handling, schemas, and observability.

redis and fred are common clients. Caching requires TTL, invalidation, key boundaries, and stampede control. Durable jobs/events need bounded retries, idempotent consumers, dead-letter handling, schemas, and observability.

// Delivery design:
// event ID + schema version
// idempotency/inbox record
// bounded retry with backoff
// acknowledge only after durable success
// dead-letter path and lag metrics
Rediscachequeueeventidempotency
Part IX — Web, APIs, Data, and Frontends

gRPC, GraphQL, WebSocket, and SSE

Tonic plus Prost is the prominent Tokio gRPC stack. async-graphql and Juniper cover GraphQL. WebSockets provide full duplex messaging; SSE is simpler for one-way browser event streams over HTTP.

Tonic plus Prost is the prominent Tokio gRPC stack. async-graphql and Juniper cover GraphQL. WebSockets provide full duplex messaging; SSE is simpler for one-way browser event streams over HTTP.

async fn events() -> axum::response::sse::Sse<
    impl futures::Stream<Item = Result<axum::response::sse::Event, Infallible>>
> {
    axum::response::sse::Sse::new(event_stream())
}
TonicProstGraphQLWebSocketSSE
Part IX — Web, APIs, Data, and Frontends

Templates, frontend bundling, and WebAssembly

Serve a separately built Vite app, embed dist assets with rust-embed/include_dir, or render HTML with Askama/Maud. Rust-to-Wasm uses wasm-bindgen and frameworks such as Leptos, Yew, or Dioxus when the trade-off is deliberate.

Serve a separately built Vite app, embed dist assets with rust-embed/include_dir, or render HTML with Askama/Maud. Rust-to-Wasm uses wasm-bindgen and frameworks such as Leptos, Yew, or Dioxus when the trade-off is deliberate.

# Typical SPA build:
cd web
npm ci
npm run build

# Rust choices:
# askama / maud
# rust-embed / include_dir
# wasm-bindgen
# leptos / yew / dioxus
frontendViteAskamaWasmLeptosYewDioxus
Part X — Testing and Tooling

Unit, integration, and documentation tests

Unit tests commonly live beside source under cfg(test). Files under tests are separate integration-test crates. Rustdoc examples can compile and run as documentation tests.

Unit tests commonly live beside source under cfg(test). Files under tests are separate integration-test crates. Rustdoc examples can compile and run as documentation tests.

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn adds_items() {
        let mut cart = Cart::default();
        cart.add(Item::new("book", 2));
        assert_eq!(cart.total_items(), 2);
    }
}
unit-testintegration-testdoctest
Part X — Testing and Tooling

Async tests and test fixtures

tokio::test runs async tests. rstest supports fixtures and parameterized cases. Testcontainers can launch real databases and brokers; wiremock/httpmock emulate HTTP boundaries.

tokio::test runs async tests. rstest supports fixtures and parameterized cases. Testcontainers can launch real databases and brokers; wiremock/httpmock emulate HTTP boundaries.

#[tokio::test]
async fn creates_user() {
    let app = test_app().await;
    let response = app.create_user("a@example.com").await;
    assert_eq!(response.status(), 201);
}
tokio-testrstesttestcontainerswiremock
Part X — Testing and Tooling

Property and snapshot testing

proptest and quickcheck generate inputs and shrink failures, making them valuable for parsers, codecs, and invariants. insta manages reviewed snapshots for stable complex output.

proptest and quickcheck generate inputs and shrink failures, making them valuable for parsers, codecs, and invariants. insta manages reviewed snapshots for stable complex output.

proptest::proptest! {
    #[test]
    fn round_trip(input in ".*") {
        let encoded = encode(&input);
        let decoded = decode(&encoded).unwrap();
        prop_assert_eq!(decoded, input);
    }
}
proptestquickcheckinstasnapshot
Part X — Testing and Tooling

Mocks, fakes, and boundaries

Prefer small traits at real boundaries and deterministic fakes with useful behavior. mockall generates interaction mocks when call verification matters. Excessive mocks couple tests to implementation sequences.

Prefer small traits at real boundaries and deterministic fakes with useful behavior. mockall generates interaction mocks when call verification matters. Excessive mocks couple tests to implementation sequences.

trait UserStore {
    fn get(&self, id: u64) -> Result<User, StoreError>;
}

struct FakeUserStore {
    users: HashMap<u64, User>,
}
mockfakemockall
Part X — Testing and Tooling

Benchmarks and profiling

Criterion provides statistically informed benchmarks. cargo-flamegraph and platform profilers reveal hot paths; heaptrack, DHAT, Valgrind, and Tokio Console help with allocation and async diagnostics.

Criterion provides statistically informed benchmarks. cargo-flamegraph and platform profilers reveal hot paths; heaptrack, DHAT, Valgrind, and Tokio Console help with allocation and async diagnostics.

fn benchmark(c: &mut criterion::Criterion) {
    c.bench_function("parse order", |b| {
        b.iter(|| parse_order(std::hint::black_box(SAMPLE)))
    });
}

// cargo flamegraph --bin api
CriterionflamegraphprofilingTokio-console
Part X — Testing and Tooling

rustfmt, Clippy, rust-analyzer, and CI

rustfmt standardizes layout; Clippy catches correctness and idiom issues; rust-analyzer powers editor analysis. Run formatting, linting, tests, and docs across the workspace and selected feature combinations.

rustfmt standardizes layout; Clippy catches correctness and idiom issues; rust-analyzer powers editor analysis. Run formatting, linting, tests, and docs across the workspace and selected feature combinations.

cargo fmt --all -- --check
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test --workspace --all-features
cargo doc --workspace --no-deps
rustfmtClippyrust-analyzerCI
Part X — Testing and Tooling

Features and dependency management

Cargo features are additive compile-time capabilities. Inspect duplicate versions and feature activation with cargo tree. Commit Cargo.lock for applications. Use cargo update deliberately and define an MSRV policy.

Cargo features are additive compile-time capabilities. Inspect duplicate versions and feature activation with cargo tree. Commit Cargo.lock for applications. Use cargo update deliberately and define an MSRV policy.

[features]
default = ["json"]
json = ["dep:serde", "dep:serde_json"]
telemetry = ["dep:tracing-opentelemetry"]

cargo tree
cargo tree -d
cargo update -p crate_name
Cargo-featuredependencyCargo.lockMSRV
Part X — Testing and Tooling

Cross-compilation, build.rs, and FFI

rustup adds targets; cross and cargo-zigbuild simplify builds. build.rs performs native discovery/generation before compilation. bindgen and cbindgen bridge C APIs. Keep unsafe FFI wrappers small and expose safe APIs.

rustup adds targets; cross and cargo-zigbuild simplify builds. build.rs performs native discovery/generation before compilation. bindgen and cbindgen bridge C APIs. Keep unsafe FFI wrappers small and expose safe APIs.

rustup target add x86_64-unknown-linux-musl
cargo build --release --target x86_64-unknown-linux-musl

// build.rs
fn main() {
    println!("cargo:rerun-if-changed=native/library.c");
}
cross-compilebuild.rsFFIbindgen
Part X — Testing and Tooling

Editions, semver, MSRV, and publishing

Editions evolve syntax and idioms without splitting library compatibility. rust-version declares MSRV. crates.io publication requires package metadata and semantic-version discipline; preflight with cargo package and publish --dry-run.

Editions evolve syntax and idioms without splitting library compatibility. rust-version declares MSRV. crates.io publication requires package metadata and semantic-version discipline; preflight with cargo package and publish --dry-run.

[package]
name = "inventory-core"
version = "0.3.0"
edition = "2024"
rust-version = "1.85"
license = "MIT OR Apache-2.0"

cargo package
cargo publish --dry-run
editionsemverMSRVcrates.io
Part X — Testing and Tooling

Documentation and API design

Use rustdoc comments with examples, links, panics/errors/safety sections, and docs.rs metadata. Public APIs should minimize unnecessary generic parameters, expose stable semantics, and use non_exhaustive where future variants/fields are likely.

Use rustdoc comments with examples, links, panics/errors/safety sections, and docs.rs metadata. Public APIs should minimize unnecessary generic parameters, expose stable semantics, and use non_exhaustive where future variants/fields are likely.

/// Loads a user.
///
/// # Errors
/// Returns `LoadError::NotFound` when the ID is absent.
///
/// # Examples
/// ```
/// # async fn demo(repo: &Repo) -> Result<(), LoadError> {
/// let user = repo.load(1).await?;
/// # Ok(()) }
/// ```
pub async fn load(&self, id: u64) -> Result<User, LoadError> { todo!() }
rustdocAPIdocs.rs
Part XI — Production Engineering

Configuration, secrets, and startup validation

Deserialize configuration once at startup and validate it before serving traffic. config and figment merge sources; clap handles flags; secrecy reduces accidental secret exposure. Avoid hidden global mutable configuration.

Deserialize configuration once at startup and validate it before serving traffic. config and figment merge sources; clap handles flags; secrecy reduces accidental secret exposure. Avoid hidden global mutable configuration.

#[derive(serde::Deserialize)]
struct Config {
    bind: std::net::SocketAddr,
    database_url: secrecy::SecretString,
}

let cfg: Config = config::Config::builder()
    .add_source(config::Environment::default().separator("__"))
    .build()?
    .try_deserialize()?;
configfigmentsecrecystartup
Part XI — Production Engineering

tracing, metrics, and OpenTelemetry

tracing records structured spans and events across sync and async code. tracing-subscriber filters and exports them. Prometheus/metrics crates expose aggregates; OpenTelemetry connects distributed traces and metrics.

tracing records structured spans and events across sync and async code. tracing-subscriber filters and exports them. Prometheus/metrics crates expose aggregates; OpenTelemetry connects distributed traces and metrics.

#[tracing::instrument(skip(pool), fields(user.id = id))]
async fn load_user(pool: &PgPool, id: i64) -> Result<User, Error> {
    tracing::info!("loading user");
    query_user(pool, id).await
}
tracingmetricsOpenTelemetryobservability
Part XI — Production Engineering

Graceful shutdown and task lifecycle

Stop accepting traffic, propagate cancellation, finish bounded in-flight work, stop producers, drain safe queues, and flush telemetry before a deadline. Readiness should turn false before termination in orchestrated systems.

Stop accepting traffic, propagate cancellation, finish bounded in-flight work, stop producers, drain safe queues, and flush telemetry before a deadline. Readiness should turn false before termination in orchestrated systems.

let token = CancellationToken::new();
let shutdown = token.clone();

let server = axum::serve(listener, app)
    .with_graceful_shutdown(async move {
        tokio::signal::ctrl_c().await.ok();
        shutdown.cancel();
    });

server.await?;
shutdownsignallifecyclereadiness
Part XI — Production Engineering

Timeouts, retries, bulkheads, and backpressure

Every remote operation needs a deadline. Retry only idempotent/safe work with bounded exponential backoff and jitter. Bound queues and concurrency, shed overload, and align resource pools with downstream capacity.

Every remote operation needs a deadline. Retry only idempotent/safe work with bounded exponential backoff and jitter. Bound queues and concurrency, shed overload, and align resource pools with downstream capacity.

let result = tokio::time::timeout(
    Duration::from_secs(2),
    client.get(url).send(),
).await??;

let permit = concurrency_limit.acquire().await?;
let result = call_dependency().await;
drop(permit);
timeoutretrybulkheadbackpressure
Part XI — Production Engineering

Security and supply chain

Use cargo-audit for RustSec advisories, cargo-deny for licenses/bans/sources, cargo-vet for review trust, and cargo-geiger to inventory unsafe code. Review proc macros and build scripts because they execute during builds.

Use cargo-audit for RustSec advisories, cargo-deny for licenses/bans/sources, cargo-vet for review trust, and cargo-geiger to inventory unsafe code. Review proc macros and build scripts because they execute during builds.

cargo audit
cargo deny check
cargo tree -d
cargo geiger

# Validate paths, URLs, redirects, archives,
# SQL, templates, commands, and deserialized input.
securitycargo-auditcargo-denysupply-chain
Part XI — Production Engineering

Containers and release profiles

Use multi-stage builds, locked dependencies, non-root runtime users, necessary CA/timezone files, and read-only filesystems where practical. Measure LTO, codegen units, stripping, and static-linking trade-offs.

Use multi-stage builds, locked dependencies, non-root runtime users, necessary CA/timezone files, and read-only filesystems where practical. Measure LTO, codegen units, stripping, and static-linking trade-offs.

[profile.release]
lto = "thin"
codegen-units = 1
strip = "symbols"

# Build:
cargo build --locked --release
DockerreleaseLTOdeployment
Part XI — Production Engineering

Performance mental model

Measure algorithms, allocations, cloning, cache behavior, syscalls, serialization, contention, and async wakeups. Borrow and batch when they simplify ownership and improve measured behavior; do not clone merely to silence the compiler.

Measure algorithms, allocations, cloning, cache behavior, syscalls, serialization, contention, and async wakeups. Borrow and batch when they simplify ownership and improve measured behavior; do not clone merely to silence the compiler.

let mut output = Vec::with_capacity(expected_size);
output.extend_from_slice(header);
for item in items {
    output.extend_from_slice(&item.to_bytes());
}

// Confirm with Criterion and profiles.
performanceallocationclonecontention
Part XI — Production Engineering

Common Rust pitfalls

Watch for unnecessary clone, Arc<Mutex<_>> as a default architecture, blocking runtime workers, holding guards across await, detached tasks, unbounded channels, careless unwrap, feature explosion, and undocumented unsafe invariants.

Watch for unnecessary clone, Arc<Mutex<_>> as a default architecture, blocking runtime workers, holding guards across await, detached tasks, unbounded channels, careless unwrap, feature explosion, and undocumented unsafe invariants.

// Review:
// ownership clear?
// guard crosses .await?
// blocking isolated?
// tasks and queues bounded?
// task failures observed?
// unsafe contract documented?
// deadlines configured?
// clippy, tests, audit, deny pass?
pitfallreviewchecklist
Part XI — Production Engineering

Coherent production stack choices

A strong async API default is Tokio + Axum + Tower + Serde + tracing + SQLx. Actix Web is a sound alternative for its ecosystem/team familiarity; Diesel for typed query composition; Tonic for gRPC; Leptos/Yew/Dioxus only when Rust frontend benefits justify complexity.

A strong async API default is Tokio + Axum + Tower + Serde + tracing + SQLx. Actix Web is a sound alternative for its ecosystem/team familiarity; Diesel for typed query composition; Tonic for gRPC; Leptos/Yew/Dioxus only when Rust frontend benefits justify complexity.

# Typical API:
tokio
axum
tower
tower-http
serde
serde_json
tracing
tracing-subscriber
sqlx
thiserror
anyhow
reqwest
uuid
time
stackrecommendationecosystem