Rust essentials

The Rust 1.98 syntax, error patterns, concurrency tools, and Cargo commands worth keeping beside the editor.

Rust 1.98 1 page when printed
Download .md

Bindings and control flow

let value = source; bind an immutable local; shadow it with another let when the type must change
let mut count = 0; permit reassignment and in-place mutation
const LIMIT: usize = 100; declare a typed compile-time constant
let Some(id) = maybe_id else { return; }; extract a matching value or leave the current control flow
match result { Ok(value) => value, Err(error) => return Err(error) } handle every variant and return the original error
while let Some(item) = stack.pop() { consume(item); } keep consuming values until the pattern stops matching

Ownership and borrowing

inspect(&value); lend shared access without transferring ownership
update(&mut value); lend exclusive mutable access until the borrow’s last use
let moved = value; transfer ownership and invalidate the old binding unless the type is Copy
let copied = number; copy a Copy value so both bindings remain usable
let cloned = value.clone(); request an explicit duplicate through Clone
let old = std::mem::take(&mut value); replace with Default::default() and take the old value
drop(guard); release an owned resource before the end of its scope

Strings and collections

fn normalize(text: &str) -> String { text.trim().to_owned() } borrow UTF-8 input and return owned text
text.chars() iterate Unicode scalar values, not grapheme clusters
text.as_bytes() borrow the underlying UTF-8 bytes without allocating
String::from_utf8(bytes)? validate UTF-8 and reuse the owned byte buffer
Vec::with_capacity(count) reserve space when the expected item count is known
counts.entry(key).or_insert(0) insert a missing map value and borrow it mutably
queue.pop_front() remove the oldest VecDeque item as an Option

Iterators and closures

items.iter() yield shared references and keep the collection
items.iter_mut() yield exclusive references for in-place updates
items.into_iter() consume the collection and yield owned items
iter.filter_map(parse).collect::<Vec<_>>() discard None values and collect successful projections
iter.collect::<Result<Vec<_>, _>>()? stop at the first error while collecting successful items
move |item| process(item, &config) capture config by value for a closure that may outlive this scope
F: FnMut(&Item) -> bool accept a repeatable predicate that may mutate captured state

Traits and types

#[derive(Debug, Clone, PartialEq)] struct Point(i32, i32); generate standard trait implementations for a data type
struct UserId(u64); give a primitive a distinct domain type with the newtype pattern
enum State<T> { Ready(T), Closed } model a closed set of states with associated data
fn show<T: Display>(value: &T) { println!("{value}"); } require a capability with a generic trait bound
trait Source { type Item; fn next(&mut self) -> Option<Self::Item>; } let each implementation choose one associated item type
Box<dyn Draw + Send> own a type-erased implementor that can cross thread boundaries
<User as Display>::fmt(&user, formatter) select a trait implementation explicitly with fully qualified syntax

Option and Result

type LoadResult<T> = Result<T, LoadError>; name a reusable result type without hiding its error
let value = operation()?; unwrap success or return a converted residual immediately
option.ok_or_else(|| Error::Missing(key))? turn absence into a lazily constructed error
option.and_then(parse) chain a function that returns another Option
result.map(transform) transform only the success value
result.map_err(Error::from) convert only the error value
matches!(status, Status::Ready(_)) test a pattern and return a boolean

anyhow and thiserror

type AppResult<T> = anyhow::Result<T>; erase concrete error types at an application boundary
read(path).with_context(|| format!("reading {path}"))? after importing anyhow::Context, add context only on failure
anyhow::bail!("invalid port: {port}"); return an ad hoc application error immediately
anyhow::ensure!(port > 0, "port must be positive"); return an application error when an invariant is false
#[derive(Debug, thiserror::Error)] enum LoadError { #[error("missing config")] Missing } define a concrete error that callers can match
#[error("cannot read {path}: {source}")] Read { path: PathBuf, #[source] source: std::io::Error } format a variant while preserving its lower-level source
Io(#[from] std::io::Error) generate From conversion and error-source reporting for a variant

Threads and shared state

let handle = std::thread::spawn(move || work(data)); move owned task data into a new operating-system thread
let result = handle.join().expect("worker panicked"); wait for a thread and surface a panic explicitly
let shared = Arc::new(Mutex::new(value)); combine shared ownership with synchronized mutation
let mut guard = shared.lock().unwrap(); acquire mutable access; handle poisoning when recovery matters
let read_guard = lock.read().unwrap(); allow concurrent readers through an RwLock guard
let (sender, receiver) = std::sync::mpsc::sync_channel(capacity); create a bounded channel whose full buffer applies backpressure
sender.send(value)?; transfer a value to the receiver or report disconnection

Async and Tokio

async fn fetch() -> Result<Data, Error> { request().await } return a future whose body runs when polled
let value = future.await?; suspend until completion and propagate failure
let handle = tokio::spawn(async move { work(data).await }); spawn an owned Send + 'static task on the runtime
let (left, right) = tokio::try_join!(load_a(), load_b())?; drive fallible futures concurrently and stop on an error
tokio::select! { value = task => value, } race enabled branches; losing futures must be cancellation-safe
let value = tokio::time::timeout(duration, operation()).await??; bound the wait and propagate both timeout and operation errors
tokio::task::spawn_blocking(|| blocking_work()).await?; move blocking work off asynchronous worker threads

Modules and Cargo

mod parser; declare a module whose source the compiler locates by module rules
use crate::parser::parse; bring an item into scope from the current crate root
pub(crate) fn helper() {} expose an item only within the current crate
pub use api::Client; re-export an item as part of the public API
cargo check --all-targets --all-features type-check every target with every feature enabled
cargo test --workspace run tests for every package in the workspace
cargo clippy --all-targets --all-features -- -D warnings lint all targets and fail on any warning

Unsafe and FFI

let ptr = &raw const value; create a raw pointer without creating an intermediate reference
let ptr = NonNull::new(ptr).ok_or(Error::Null)?; reject null without claiming the pointer is otherwise valid
let value = unsafe { ptr.read() }; read only after proving alignment, initialization, and validity
let slice = unsafe { std::slice::from_raw_parts(ptr, len) }; build a slice only after proving the full memory-range contract
#[repr(C)] struct Header { tag: u32, len: usize } request C-compatible field layout for an FFI data structure
unsafe extern "C" { fn strlen(s: *const c_char) -> usize; } declare a foreign function whose call contract Rust cannot verify
let text = unsafe { CStr::from_ptr(ptr) }; borrow a C string only after proving termination and pointer validity

Say it precisely to your AI

rules pack · Rust

Rust rules for your coding agent

Download the track's pitfalls and review checks in the format your coding agent reads.