| name | rust-design-patterns |
| description | Idiomatic Rust design patterns, idioms, and anti-patterns. Use when structuring Rust code, choosing between patterns like Newtype/Builder/Typestate, applying idioms like mem::take or borrowed-type arguments, or avoiding common anti-patterns. |
Rust Design Patterns
Based on Rust Design Patterns book, Effective Rust, and community idioms.
When to Use This Skill
- Structuring a new Rust module or crate
- Choosing the right abstraction pattern
- Avoiding common anti-patterns (Deref polymorphism, clone-to-satisfy-borrow-checker)
- Making APIs ergonomic and hard to misuse
- Encoding invariants in the type system
Idioms
Use Borrowed Types for Arguments
Prefer &str over &String, &[T] over &Vec<T>, &T over &Box<T>:
fn process(data: &str) { ... }
fn sum(nums: &[i32]) { ... }
Default Trait
#[derive(Default)]
struct Config {
port: u16,
debug: bool,
name: String,
}
let cfg = Config { port: 8080, ..Config::default() };
mem::take / mem::replace
Move out of a &mut without clone:
use std::mem;
fn extract_name(user: &mut User) -> String {
mem::take(&mut user.name)
}
let old = mem::replace(&mut self.state, State::Done);
Destructuring for Clarity
let Point { x, y } = point;
let (first, rest) = slice.split_first().unwrap();
let Config { port, .. } = config;
Constructor Conventions
impl Connection {
pub fn new(addr: &str) -> Self { ... }
pub fn builder() -> ConnectionBuilder { ... }
pub fn with_timeout(addr: &str, ms: u64) -> Self { ... }
}
Design Patterns
Newtype
Wrap a type for type safety without runtime cost:
struct UserId(u64);
struct OrderId(u64);
Also used to implement external traits on external types.
Builder
pub struct RequestBuilder {
url: String,
method: Method,
headers: Vec<(String, String)>,
timeout: Option<Duration>,
}
impl RequestBuilder {
pub fn new(url: impl Into<String>) -> Self { ... }
pub fn method(mut self, m: Method) -> Self { self.method = m; self }
pub fn header(mut self, k: impl Into<String>, v: impl Into<String>) -> Self { ... }
pub fn timeout(mut self, d: Duration) -> Self { self.timeout = Some(d); self }
pub fn build(self) -> Result<Request, BuildError> { ... }
}
Typestate
Encode state machine transitions at compile time:
struct Tcp<S> { socket: RawFd, _state: PhantomData<S> }
struct Closed;
struct Connected;
struct Listening;
impl Tcp<Closed> {
fn connect(self, addr: &str) -> io::Result<Tcp<Connected>> { ... }
fn listen(self, port: u16) -> io::Result<Tcp<Listening>> { ... }
}
impl Tcp<Connected> {
fn send(&self, data: &[u8]) -> io::Result<usize> { ... }
fn close(self) -> Tcp<Closed> { ... }
}
impl Tcp<Listening> {
fn accept(&self) -> io::Result<Tcp<Connected>> { ... }
}
Strategy (via Trait Objects or Generics)
trait Compressor { fn compress(&self, data: &[u8]) -> Vec<u8>; }
struct Pipeline { compressor: Box<dyn Compressor> }
struct Pipeline<C: Compressor> { compressor: C }
Visitor
trait Visitor {
fn visit_file(&mut self, file: &File);
fn visit_dir(&mut self, dir: &Dir);
}
trait Visitable {
fn accept(&self, visitor: &mut dyn Visitor);
}
RAII Guards
pub struct MutexGuard<'a, T> { lock: &'a Mutex<T> }
impl<T> Drop for MutexGuard<'_, T> {
fn drop(&mut self) { self.lock.release(); }
}
Anti-Patterns
Deref Polymorphism
Don't use Deref/DerefMut to simulate inheritance:
impl Deref for Child { type Target = Parent; ... }
struct Child { parent: Parent }
impl Child {
fn parent_method(&self) -> &str { self.parent.method() }
}
Clone to Satisfy the Borrow Checker
let key = map.keys().next().unwrap().clone();
map.remove(&key);
if let Some(key) = map.keys().next().copied() {
map.remove(&key);
}
Stringly Typed APIs
fn set_color(color: &str) { ... }
enum Color { Red, Green, Blue, Custom(u8, u8, u8) }
fn set_color(color: Color) { ... }
Reference Map
references/idioms.md — borrowed types, Default, mem::take, destructuring
references/patterns.md — Newtype, Builder, Typestate, Visitor, RAII
references/anti-patterns.md — Deref polymorphism, clone-borrow, stringly-typed
Key References