Skip to content

Rust in Dependency Order

Rust becomes easier when learned in dependency order. Do not memorize features independently; learn which guarantee each feature buys.

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

  • 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> or BTreeSet<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 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> and Mutex<T> permit checked interior mutation in different settings.

Start with ordinary ownership. Add a smart pointer because a concrete sharing requirement exists.

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.

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.

  1. values, expressions, structs, and enums;
  2. Option, Result, pattern matching, and error propagation;
  3. ownership and borrowing;
  4. collections and iterators;
  5. traits and generics;
  6. modules and workspaces;
  7. closures and async;
  8. smart pointers and concurrency;
  9. FFI or unsafe only when the system requires it.

Use the official Rust book for comprehensive syntax and this book’s projects for applied repetition.