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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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 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.
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.
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.
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.
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.
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