Rust in Dependency Order
Rust becomes easier when learned in dependency order. Do not memorize features independently; learn which guarantee each feature buys.
Values, mutability, and expressions
Section titled “Values, mutability, and expressions”Bindings are immutable by default:
let model = "small-local";let mut attempts = 0;attempts += 1;Blocks and control flow are expressions:
let priority = if risk.is_high() { 10 } else { 3 };
let label = match evidence { Evidence::Text(_) => "text", Evidence::Image(_) => "image", Evidence::Audio(_) => "audio", Evidence::Video(_) => "video",};Prefer expressions that produce a value over mutating a placeholder through many branches.
Structs describe products; enums describe alternatives
Section titled “Structs describe products; enums describe alternatives”struct Budget { model_calls: u32, tool_calls: u32, deadline: Instant,}
enum StopReason { Completed, Refused, Cancelled, BudgetExhausted,}If exactly one alternative is valid, use an enum—not several booleans.
Option and Result
Section titled “Option and Result”Option<T> means a value may be absent. Result<T, E> means an operation may fail with information.
fn find_evidence(id: EvidenceId) -> Option<EvidenceItem>;fn decode(bytes: &[u8]) -> Result<DecodedMedia, DecodeError>;Use ? to propagate an error while preserving the current function’s contract:
fn load_task(path: &Path) -> Result<Task, TaskError> { let bytes = std::fs::read(path)?; let raw: RawTask = serde_yaml::from_slice(&bytes)?; Task::try_from(raw)}Do not use unwrap() on user, network, media, model, or persistent data. It is acceptable in a test
where panic is the assertion or in a truly established invariant with a clear explanation.
Collections encode access patterns
Section titled “Collections encode access patterns”Vec<T>for ordered contiguous values;VecDeque<T>for queues at both ends;HashMap<K, V>for fast keyed lookup;BTreeMap<K, V>for deterministic ordering and ranges;HashSet<T>orBTreeSet<T>for membership;BinaryHeap<T>for priority work.
Choose based on the operation, not familiarity. Deterministic maps are useful for reproducible traces and snapshots.
Iterators separate transformation from storage
Section titled “Iterators separate transformation from storage”let valid_citations: Vec<_> = claims .iter() .flat_map(|claim| &claim.evidence) .filter(|citation| manifest.contains(citation.evidence_id)) .collect();Iterator adapters are lazy until consumed. Use them when the pipeline reads clearly. A loop is better when control flow, mutation, or error context becomes obscure.
Fallible iteration:
let tasks: Result<Vec<_>, TaskError> = paths .iter() .map(|path| load_task(path)) .collect();Closures capture an environment
Section titled “Closures capture an environment”Closures power callbacks, iterator transforms, and configurable policy:
let within_budget = |cost: u64| spent + cost <= limit;The Fn, FnMut, and FnOnce traits describe whether a closure borrows, mutates, or consumes captured
state. Async tasks often use move to own their captures.
Smart pointers express ownership strategies
Section titled “Smart pointers express ownership strategies”Box<T>owns a value with stable heap allocation or erases recursive size.Rc<T>shares immutable ownership on one thread.Arc<T>shares ownership across threads.RefCell<T>andMutex<T>permit checked interior mutation in different settings.
Start with ordinary ownership. Add a smart pointer because a concrete sharing requirement exists.
Modules control visibility
Section titled “Modules control visibility”Keep public APIs small:
pub(crate) struct ProviderWireResponse { /* ... */ }pub struct ModelResponse { /* normalized domain form */ }pub is a maintenance commitment. Prefer constructors and methods that preserve invariants over public
fields that callers can assemble incorrectly.
Macros, unsafe, and FFI
Section titled “Macros, unsafe, and FFI”Derive macros such as serde::Deserialize, thiserror::Error, and clap::Parser remove mechanical
code. Inspect generated behavior when it affects security or wire compatibility.
Most AI application code needs no unsafe. Native inference and media libraries may use it internally.
Keep FFI behind a small safe wrapper that validates:
- pointers and lengths;
- thread-safety assumptions;
- object lifetime;
- error codes;
- ownership of allocated memory.
Document each unsafe block with the invariant that makes it valid and test the boundary aggressively.
The practice order
Section titled “The practice order”- values, expressions, structs, and enums;
Option,Result, pattern matching, and error propagation;- ownership and borrowing;
- collections and iterators;
- traits and generics;
- modules and workspaces;
- closures and async;
- smart pointers and concurrency;
- FFI or
unsafeonly when the system requires it.
Use the official Rust book for comprehensive syntax and this book’s projects for applied repetition.