Rust Patterns
Ownership and Borrowing
Rust's core: each value has one owner; borrow with & (shared) or &mut (exclusive).
fn main() {
let s1 = String::from("hello"); // s1 owns the string
let s2 = &s1; // s2 borrows s1 (shared)
println!("{} {}", s1, s2); // both valid
let mut v = vec![1, 2, 3];
let first = &v[0]; // immutable borrow
// v.push(4); // ERROR: cannot mutate while borrowed
println!("{}", first);
v.push(4); // borrow ended; mutation OK now
}
fn take_ownership(s: String) -> usize { s.len() } // s dropped here
fn borrow(s: &str) -> usize { s.len() } // caller retains ownership
Enums and Pattern Matching
Rust enums are algebraic data types — use them for state machines and domain modeling.
#[derive(Debug)]
enum Shape {
Circle { radius: f64 },
Rectangle { width: f64, height: f64 },
Triangle(f64, f64, f64),
}
impl Shape {
fn area(&self) -> f64 {
match self {
Shape::Circle { radius } => std::f64::consts::PI * radius * radius,
Shape::Rectangle { width, height } => width * height,
Shape::Triangle(base, height, _) => 0.5 * base * height,
}
}
}
// if let for single-variant extraction
fn print_radius(shape: &Shape) {
if let Shape::Circle { radius } = shape {
println!("radius: {radius}");
}
}
// while let for iterating until None
fn drain_queue(queue: &mut Vec<String>) {
while let Some(item) = queue.pop() {
println!("processing: {item}");
}
}
Traits
Define shared behavior. Prefer trait objects (dyn Trait) for runtime dispatch, generics for compile-time.
trait Summarize {
fn summary(&self) -> String;
fn title(&self) -> &str { "untitled" } // default implementation
}
struct Article { headline: String, content: String }
impl Summarize for Article {
fn summary(&self) -> String {
format!("{}: {}...", self.headline, &self.content[..50.min(self.content.len())])
}
fn title(&self) -> &str { &self.headline }
}
// Generic function — monomorphized at compile time (zero cost)
fn print_summary<T: Summarize>(item: &T) {
println!("{}", item.summary());
}
// Trait object — runtime dispatch
fn print_all(items: &[&dyn Summarize]) {
for item in items { println!("{}", item.summary()); }
}
// Multiple trait bounds
fn notify(item: &(impl Summarize + std::fmt::Debug)) {
println!("{:?}: {}", item, item.summary());
}
Struct Patterns
Builder pattern for complex construction; newtype for type safety.
// Builder pattern
#[derive(Debug, Default)]
struct RequestBuilder {
url: String,
timeout_secs: u64,
headers: Vec<(String, String)>,
}
impl RequestBuilder {
fn new(url: impl Into<String>) -> Self {
Self { url: url.into(), timeout_secs: 30, ..Default::default() }
}
fn timeout(mut self, secs: u64) -> Self { self.timeout_secs = secs; self }
fn header(mut self, k: impl Into<String>, v: impl Into<String>) -> Self {
self.headers.push((k.into(), v.into())); self
}
}
// Newtype for type safety
struct UserId(u64);
struct OrderId(u64);
// Cannot accidentally pass UserId where OrderId expected
Option and Result Combinators
Avoid explicit match when combinators express intent more clearly.
// Option combinators
let name: Option<String> = find_user(42).map(|u| u.name.clone());
let display = find_user(42)
.filter(|u| u.active)
.map(|u| u.name)
.unwrap_or_else(|| "anonymous".to_string());
// Result combinators + ? operator
fn get_profile(id: u64) -> Result<Profile, AppError> {
let user = find_user(id).ok_or(AppError::UserNotFound)?;
let profile = load_profile(&user).map_err(AppError::Db)?;
Ok(profile)
}
// Collect Vec<Result<T, E>> into Result<Vec<T>, E>
let ids = vec!["1", "2", "abc"];
let parsed: Result<Vec<u64>, _> = ids.iter()
.map(|s| s.parse::<u64>())
.collect();
Iterators
Prefer iterator chains over explicit loops; they compile to the same code.
let data = vec![1, 2, 3, 4, 5, 6];
let result: Vec<i32> = data.iter()
.filter(|&&x| x % 2 == 0)
.map(|&x| x * x)
.collect();
let sum: i32 = data.iter().filter(|&&x| x > 2).map(|&x| x * x).sum();
// flat_map to flatten nested iterables
let words = vec!["hello world", "foo bar"];
let tokens: Vec<&str> = words.iter()
.flat_map(|s| s.split_whitespace())
.collect();
// zip two iterators
let keys = vec!["a", "b", "c"];
let vals = vec![1, 2, 3];
let map: std::collections::HashMap<_, _> = keys.into_iter().zip(vals).collect();
Common Anti-Patterns
- Cloning to avoid borrow issues — understand the borrow checker instead of cloning reflexively
- Panicking with
.unwrap()on user-facing paths — use?or proper error handling - Mutable global state with
static mut— useOnceLock,Mutex, or dependency injection - Ignoring
#[must_use]warnings — always handleResultandOptionreturn values - Over-using
Box<dyn Trait>— prefer generics when the set of types is known at compile time