agentleFS
botirkhaltaevbotirkhaltaev/botir-skills/skills/rust-design-patterns/SKILL.md

rust-design-patterns

Skill4 starsChanged 3 months agoApache-2.0View on GitHub

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.

We split this skill into the rules it gives an agent

2 rules. 0 are common, 0 are distinctive, and 2 are very specific to this project.

  • 0

    Common

    Most of the 3 edited versions we read keep these.

  • 0

    Distinctive

    This author's own choices. Worth a look.

  • 2

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Prefer &str over &String, &[T] over &Vec<T>, &T over &Box<T>:

    line 22
  2. Very specific:

    Don't use Deref/DerefMut to simulate inheritance:

    line 175

“Common” is measured: kept by at least 6 in 10 of the 3 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

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) { ... }  // accepts &String, &str, String slice
fn sum(nums: &[i32]) { ... }    // accepts &Vec<i32>, &[i32], arrays

Default Trait

#[derive(Default)]
struct Config {
    port: u16,       // 0
    debug: bool,     // false
    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)  // leaves empty String behind
}

// Replace with specific value
let old = mem::replace(&mut self.state, State::Done);

Destructuring for Clarity

let Point { x, y } = point;
let (first, rest) = slice.split_first().unwrap();

// Ignore fields
let Config { port, .. } = config;

Constructor Conventions

impl Connection {
    pub fn new(addr: &str) -> Self { ... }           // simple
    pub fn builder() -> ConnectionBuilder { ... }    // complex
    pub fn with_timeout(addr: &str, ms: u64) -> Self { ... }  // variant
}

Design Patterns

Newtype

Wrap a type for type safety without runtime cost:

struct UserId(u64);
struct OrderId(u64);

// fn process(user: UserId, order: OrderId) — can't mix up arguments

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>> { ... }
}
// Can't call .send() on a Closed socket — compile error

Strategy (via Trait Objects or Generics)

trait Compressor { fn compress(&self, data: &[u8]) -> Vec<u8>; }

struct Pipeline { compressor: Box<dyn Compressor> }
// or with generics:
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(); }
}
// Lock released automatically when guard goes out of scope

Anti-Patterns

Deref Polymorphism

Don't use Deref/DerefMut to simulate inheritance:

// BAD: using Deref to make Child act like Parent
impl Deref for Child { type Target = Parent; ... }

// GOOD: use composition + delegation or traits
struct Child { parent: Parent }
impl Child {
    fn parent_method(&self) -> &str { self.parent.method() }
}

Clone to Satisfy the Borrow Checker

// BAD: cloning just to avoid borrow conflicts
let key = map.keys().next().unwrap().clone();
map.remove(&key);

// GOOD: restructure control flow
if let Some(key) = map.keys().next().copied() {
    map.remove(&key);
}

Stringly Typed APIs

// BAD
fn set_color(color: &str) { ... }

// GOOD
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

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.