Skip to content

Instantly share code, notes, and snippets.

@erhangundogan
Created September 15, 2025 07:45
Show Gist options
  • Select an option

  • Save erhangundogan/78e1d888bd9cc12fe46e2f1a19926266 to your computer and use it in GitHub Desktop.

Select an option

Save erhangundogan/78e1d888bd9cc12fe46e2f1a19926266 to your computer and use it in GitHub Desktop.
Rust Abstractions

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); // 5

RefCell<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()); // 1

OnceCell<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)
    }
}

How to pick the right one

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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment