Option<T>
Represents “something or nothing”. Optional function arguments, nullable values.
fn maybe_double(x: Option<i32>) -> Option<i32> {
x.map(|v| v * 2)
}
assert_eq!(maybe_double(Some(3)), Some(6));
assert_eq!(maybe_double(None), None);Result<T, E>
Represents success (Ok) or failure (Err). Error handling in I/O, parsing, etc.
use std::fs;
fn read_first_line(path: &str) -> Result<String, std::io::Error> {
let mut file = fs::File::open(path)?;
use std::io::{self, Read};
let mut contents = String::new();
file.read_to_string(&mut contents)?;
Ok(contents.lines().next().unwrap_or("").to_owned())
}Iterator trait
Lazy, composable streams of values. Chaining map, filter, fold etc.
let evens: Vec<_> = (1..10).filter(|x| x % 2 == 0).collect();
assert_eq!(evens, vec![2,4,6,8]);Closures & Fn traits
Inline anonymous functions that capture environment. Callbacks, combinators.
fn apply<F: Fn(i32) -> i32>(f: F, x: i32) -> i32 { f(x) }
let add_three = |x| x + 3;
assert_eq!(apply(add_three, 7), 10);async/await + Future
Non‑blocking, concurrent code. Network servers, timers.
use tokio::time::{sleep, Duration};
async fn sleepy() {
sleep(Duration::from_secs(1)).await;
}
#[tokio::main]
async fn main() {
tokio::join!(sleepy(), sleepy());
}Channels (mpsc)
Message passing between threads. Producer/consumer pipelines.
use std::sync::mpsc::{self, Sender};
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || { tx.send(42).unwrap(); });
println!("got {}", rx.recv().unwrap());
} Box<T>
Heap allocation + ownership transfer. Polymorphic recursion, large structs.
enum List {
Nil,
Cons(i32, Box<List>),
}
fn len(l: &List) -> usize {
match l {
List::Nil => 0,
List::Cons(_, tail) => 1 + len(tail),
}
}Rc<T> / Arc<T>
Reference‑counted shared ownership (single‑/multi‑thread). Shared read‑only data, tree structures.
use std::rc::Rc;
let a = Rc::new(5);
let b = Rc::clone(&a);
println!("{}", *b); // 5RefCell<T> / Mutex<T>
Interior mutability (single‑/multi‑thread). Mutable state behind shared refs.
use std::cell::RefCell;
let data = RefCell::new(0);
{
let mut d = data.borrow_mut();
*d += 1;
}
println!("{}", data.borrow()); // 1OnceCell<T> / Lazy<T>
One‑time initialization (thread‑safe). Global configuration, expensive constants.
use once_cell::sync::Lazy;
static CONFIG: Lazy<Config> = Lazy::new(|| { /* load from file */ Config {} });HashMap<K,V> / BTreeMap<K,V>
Associative containers (hash or ordered). Caches, symbol tables
use std::collections::HashMap;
let mut m = HashMap::new();
m.insert("a", 1);
assert_eq!(m.get(&"a"), Some(&1));VecDeque<T>
Double‑ended queue (push/pop front/back). Work‑stealing, sliding windows.
use std::collections::VecDeque;
let mut q = VecDeque::new();
q.push_back(1);
q.push_front(0);
assert_eq!(q.pop_back(), Some(1));BTreeSet<T> / HashSet<T>
Unordered or ordered sets. Unique collections, membership tests.
use std::collections::HashSet;
let mut s = HashSet::new();
s.insert("foo");
assert!(s.contains(&"foo"));thiserror::Error / anyhow::Result
Declarative error types & context‑rich errors. Library authors, CLI tools.
use thiserror::Error;
#[derive(Error, Debug)]
enum MyErr {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Parse error: {0}")]
Parse(String),
}
fn parse_int(s: &str) -> Result<i32, MyErr> {
s.parse::<i32>().map_err(|e| MyErr::Parse(e.to_string()))
}PhantomData<T>
Zero‑size marker for generic type safety. Lifetimes, ownership without data.
struct Wrapper<T>(i32, std::marker::PhantomData<T>);
impl<T> Wrapper<T> {
fn new(v: i32) -> Self {
Self(v, std::marker::PhantomData)
}
}| Problem | Suggested abstraction |
|---|---|
| “Maybe I have a value or not” | Option |
| “Something can fail; let the caller decide what to do” | Result |
| “I want to lazily transform a sequence of items” | Iterator (plus combinators) |
| “I need a closure to pass around as a callback” | Fn, FnMut, FnOnce |
| “I’m writing async code” | async fn, Future, .await |
| “Two threads need to talk via messages” | mpsc::channel, or crossbeam |
| “I need heap allocation for a recursive data structure” | Box<T> |
| “Many parts of the program need shared read‑only access” | Rc<T> or Arc<T> |
| “I need shared mutable state behind a reference” | RefCell<T> or Mutex<T> |
| “I want to compute something once and reuse it” | OnceCell / Lazy |
| “I need a key‑value store” | HashMap or BTreeMap |
| “I need a FIFO queue with cheap pushes/pops at both ends” | VecDeque |
| “I need a set of unique items” | HashSet or BTreeSet |
| “I’m building a library that reports rich errors” | thiserror, anyhow |
| “I need a generic wrapper that doesn’t actually store the type” | PhantomData |
These abstractions form the core of “Rust‑ish” code. Once you’re comfortable with them, most other patterns (e.g., async-stream, tokio::sync::watch, parking_lot locks, etc.) feel like natural extensions. Happy coding!