Skip to content
Back to skills

Advanced Rust Patterns

ASecurity

Expert-level idiomatic Rust API design — the patterns that make illegal states unrepresentable and abstractions zero-cost. Type-state programming (PhantomData state machines), newtype + sealed + extension traits, typestate builders, RAII guards & Drop ordering, the interior-mutability decision tree (Cell/RefCell/Mutex/RwLock/Atomic/OnceLock), enum-driven state machines, error architecture (thiserror vs anyhow), dyn vs generic dispatch & object safety, the impl Trait / GAT / RPITIT toolbox, an...

  • 2 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 24, 2026
toolsrustgonodetestingrefactoringapiperformance

Works with

  • cli
  • api

Security analysis

A100/100

Pro scans all 9 files and shows the line behind each finding

Scanned October 2, 2026

npx -y skills add curiositech/port-daddy --skill advanced-rust-patterns --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Advanced Rust Patterns?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Advanced Rust Patterns
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/curiositech-advanced-rust-patterns/badge)](https://www.skillsdirectory.com/skills/curiositech-advanced-rust-patterns)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: advanced-rust-patterns
description: >
  Expert-level idiomatic Rust API design — the patterns that make illegal states
  unrepresentable and abstractions zero-cost. Type-state programming (PhantomData
  state machines), newtype + sealed + extension traits, typestate builders, RAII
  guards & Drop ordering, the interior-mutability decision tree
  (Cell/RefCell/Mutex/RwLock/Atomic/OnceLock), enum-driven state machines,
  error architecture (thiserror vs anyhow), dyn vs generic dispatch & object safety,
  the impl Trait / GAT / RPITIT toolbox, and Deref-for-smart-pointers (and its abuse).
  Use when designing a Rust library's public API, encoding invariants in the type
  system, choosing a dispatch or error strategy, or reviewing Rust for idiom.
  NOT for borrow-checker firefighting / toolchain / test-runner workflow
  (use rust-development-workflow), NOT for pd-console GPUI rendering/layout/panes
  (use gpui-rust-console), NOT for app packaging/notarization (use rust-app-distribution).
license: Apache-2.0
metadata:
  version: 0.1.0
  author: port-daddy
  tags: [rust, api-design, typestate, traits, error-handling, interior-mutability, dispatch, idioms]
  category: Code Quality & Testing
  argument-hint: '[topic: typestate|newtype|builder|raii|interior-mut|errors|dispatch|impl-trait]'
  pairs-with: [rust-development-workflow, gpui-rust-console, refactoring-surgeon]
---

# Advanced Rust Patterns

**Activation boundary:** use this for public API invariants, trait contracts, dispatch, error types, and state modeling. If the central question is which container owns related data, route to `rust-data-structures-advanced`. If an existing path is slow, route to `rust-performance-and-idioms`. Routine compiler feedback belongs to `rust-development-workflow`.

The patterns here share one thesis: **push correctness into the type system so the
compiler rejects misuse, then make the abstraction cost nothing at runtime.** A
type-state builder makes a missing required field a *compile error*, not a runtime
`panic`. A sealed trait lets you add methods later without breaking downstream. A
newtype `NodeId(String)` makes "passing a DAG id where a skill id is expected" stop
compiling. None of it shows up in the generated assembly. This skill is for **API
design and idiom**, not for fighting the borrow checker (that is
`rust-development-workflow`) — it assumes you already write compiling Rust and want it to
be *unmistakable*.

## When to Use

✅ **Use for**:
- Designing a library's public surface: traits, newtypes, builders, error types
- Encoding a state machine or protocol so invalid transitions don't compile
- Choosing interior mutability (`Cell` vs `RefCell` vs `Mutex`/`RwLock` vs atomics vs `OnceLock`)
- Deciding `dyn Trait` vs `<T: Trait>` (object safety, dispatch cost) — e.g. a `Box<dyn Pane>` registry
- Error architecture: `thiserror` enum for a library vs `anyhow` for a binary
- Reaching for `impl Trait` / GATs / RPITIT and knowing the API-compatibility footguns
- Reviewing Rust for idiom ("is this `Deref` a smart pointer or fake inheritance?")

❌ **NOT for**:
- Borrow-checker errors, async lifetime puzzles, `cargo`/`clippy`/test workflow → `rust-development-workflow`
- GPUI rendering, Taffy layout, pane/Block contract in `core/pd-console` → `gpui-rust-console`
- Shipping/signing/notarizing a macOS Rust app → `rust-app-distribution`
- Generic "how do I learn Rust" — this is expert idiom, not a tutorial

## Decision Points

```mermaid
flowchart TD
  Q["What are you designing?"] --> SM{"A state machine<br/>or protocol?"}
  SM -->|"states known at compile time,<br/>caller drives transitions"| TS["Type-state: PhantomData‹S›<br/>+ transitions that consume self<br/>→ ref 01"]
  SM -->|"states change at runtime<br/>from data/events"| EN["enum State + match<br/>(data-driven) → ref 02"]
  Q --> WRAP{"Wrapping a value<br/>to add meaning/<br/>invariant/trait?"}
  WRAP -->|"distinct type, hide repr"| NT["Newtype + sealed/extension traits<br/>→ ref 01"]
  WRAP -->|"many optional ctor params"| BU["Typestate builder (bon/typed-builder)<br/>→ ref 01"]
  Q --> MUT{"Need to mutate<br/>through a shared ref?"}
  MUT -->|"single thread"| ST{"Copy / whole-value?"}
  ST -->|yes| CELL["Cell‹T› (never panics) → ref 02"]
  ST -->|no| RC["RefCell‹T› (runtime borrow, can panic) → ref 02"]
  MUT -->|"multi thread"| MT{"Access shape?"}
  MT -->|"one counter/flag"| AT["Atomic → ref 02"]
  MT -->|"many read / rare write"| RW["RwLock → ref 02"]
  MT -->|"exclusive"| MX["Mutex → ref 02"]
  MT -->|"init once, read forever"| OL["OnceLock / LazyLock → ref 02"]
  Q --> ERR{"Producing errors?"}
  ERR -->|"library — callers match variants"| TE["thiserror enum, #[from] → ref 03"]
  ERR -->|"app/binary — just bubble up"| AN["anyhow + .context() → ref 03"]
  Q --> DISP{"Calling a trait method<br/>on heterogeneous types?"}
  DISP -->|"collection of mixed types,<br/>plugin registry"| DYN["Box‹dyn Trait› — check object safety → ref 04"]
  DISP -->|"hot path, one type per call site"| GEN["Generic ‹T: Trait› (monomorphized) → ref 04"]
```

## Core Capabilities

| Pattern | Use it to… | Replaces the anti-pattern… | Ref |
|---------|-----------|----------------------------|-----|
| **Type-state** | Make invalid call *order* a compile error | Runtime `assert!(self.initialized)` | 01 |
| **Newtype** | Give a primitive identity + invariants | Passing bare `String`/`u64` everywhere (stringly-typed) | 01 |
| **Sealed trait** | Add trait methods later, non-breaking | A trait downstream can implement, freezing your API | 01 |
| **Extension trait** | Add methods to a foreign type | A free `fn` that reads worse at the call site | 01 |
| **Typestate builder** | Force required fields at compile time | `Default` + runtime "field X was None" panic | 01 |
| **RAII guard** | Tie cleanup to scope exit | Manual `unlock()`/`close()` you can forget | 02 |
| **Interior mutability** | Mutate through `&self`, the right way | Reaching for `unsafe` or `Arc<Mutex>` reflexively | 02 |
| **Enum state machine** | Model runtime states exhaustively | A pile of `bool`s + `Option`s (illegal combos) | 02 |
| **thiserror / anyhow** | Match the error tool to lib vs app | `Box<dyn Error>` everywhere, or `.unwrap()` | 03 |
| **dyn vs generic** | Trade dispatch cost vs code size knowingly | Cargo-culting `Box<dyn>` into a hot loop | 04 |
| **impl Trait / GAT** | Name unnameable types; lend borrows | Boxing a closure/iterator you didn't need to | 04 |
| **Deref (smart ptr)** | Transparent pointer-like wrapper | `Deref` as fake inheritance (deref polymorphism) | 04 |

## Anti-Patterns (Novice vs Expert)

### Stringly-typed / primitive-obsessed IDs
**Novice**: `fn run(node_id: String, dag_id: String)` — two `String`s, easy to swap.
**Expert**: `NodeId(String)` and `DagId(String)` newtypes. `run(dag, node)` vs `run(node, dag)`
now fails to compile. Zero runtime cost; the wrapper is erased. (In a gpui pane registry,
`PaneId(u16)` vs a raw nav index prevents the classic "NAV slot != producer slot" bug from
even type-checking.) See ref 01.
**Detection**: public functions taking ≥2 same-typed primitive params that mean different things.

### `Arc<Mutex<T>>` as the reflexive answer to "shared mutable state"
**Novice**: wrap everything in `Arc<Mutex<T>>` so "any thread can touch it."
**Expert**: walk the decision tree. Single thread? `Cell`/`RefCell` — no lock at all. One
integer across threads? `AtomicU64`. Read-mostly? `RwLock`. Init-once-read-forever? `OnceLock`.
A `Mutex` held across an `.await` or a render call is the canonical multi-agent / GPUI stall.
See ref 02.
**Detection**: `Mutex`/`RwLock` chosen before the access pattern (Copy? read/write ratio? thread count?) was named.

### `Deref` to fake inheritance ("deref polymorphism")
**Novice**: `impl Deref for Wrapper { type Target = Inner; … }` so `wrapper.inner_method()` "just works."
**Expert**: `Deref` means *smart pointer* — "what's inside the box." Using it for inheritance is
implicit magic with no true subtyping: trait bounds on `Inner` do **not** apply to `Wrapper`, and
inside `Inner::method` `self` is `Inner`, not `Wrapper`, so nothing overrides. Use a real method or
trait. The std docs themselves warn to implement `Deref` "only when deref coercion is desirable."
See ref 04.
**Detection**: a `Deref` impl whose `Target` is not a thing the type *contains-as-a-pointer*.

### `Option<T>` to "make a builder field optional" (with `typed-builder`)
**Novice**: assumes wrapping a field in `Option` makes the builder skip it.
**Expert**: in `typed-builder`, fields are **required by default** — `Option<T>` is still required
unless you write `#[builder(default)]`. (In `bon`, `Option<T>` *is* auto-optional — the two crates
differ, and assuming the wrong one is a real bug.) The whole point of a typestate builder is that
`.build()` **does not exist as a method** until every required slot is set. See ref 01.
**Detection**: a `#[derive(TypedBuilder)]` field that's `Option<T>` with no `#[builder(default)]` and the author expected it optional.

## Quality Gates

```
□ No public fn takes two same-typed primitives meaning different things (newtype them) — ref 01
□ Traits meant to stay closed are sealed (private::Sealed supertrait) — ref 01
□ State machines: compile-time order → typestate; runtime data → enum + exhaustive match — ref 01/02
□ Interior mutability picked from the access pattern, not by habit (Cell/RefCell/Atomic/RwLock/Mutex/OnceLock) — ref 02
□ No lock (Mutex/RwLock guard, RefCell borrow) held across .await or a render/notify call — ref 02
□ Drop order understood: struct fields drop in declaration order; locals in REVERSE — ref 02
□ Never call .drop() (E0040); use std::mem::drop(x) — ref 02
□ Library errors = thiserror enum (matchable, #[from] for ?); binary errors = anyhow + .context() — ref 03
□ No anyhow::Error in a library's PUBLIC return type (callers can't match) — ref 03
□ dyn vs generic chosen on object-safety + dispatch cost, not reflex; check dyn-compatibility rules — ref 04
□ Deref implemented ONLY for smart-pointer-like wrappers, never for inheritance — ref 04
□ Any type with an unused generic/lifetime param carries the right PhantomData marker (variance/dropck/Send) — ref 01
□ cargo clippy -- -W clippy::pedantic is clean on the new API surface
```

## Worked Example — A typestate connection that can't be misused

A `Connection` that must be opened before a request is sent, and where calling
`request()` on a closed connection is a **compile error**, not a runtime check.

```rust
use std::marker::PhantomData;

// Uninhabited markers: no value of these can ever exist — state is purely type-level.
enum Closed {}
enum Open {}

struct Connection<S> {
    socket: TcpStream,
    _state: PhantomData<S>,
}

impl Connection<Closed> {
    fn connect(addr: &str) -> std::io::Result<Connection<Open>> {
        let socket = TcpStream::connect(addr)?;
        Ok(Connection { socket, _state: PhantomData })
    }
}

impl Connection<Open> {
    // request() exists ONLY in the Open state.
    fn request(&mut self, body: &[u8]) -> std::io::Result<Vec<u8>> { /* ... */ }

    // close() consumes self -> the Open handle is gone, so you cannot request() after.
    fn close(self) -> Connection<Closed> {
        Connection { socket: self.socket, _state: PhantomData }
    }
}
```

**What the novice would do**: a `bool is_open` field and `if !self.is_open { panic!("closed") }`
at the top of `request()`. That defers the error to runtime and to whoever hits that path in prod.

**What the expert gets**: `Connection::<Closed>` literally has no `request` method, and `close(self)`
moves the `Open` handle away, so use-after-close fails at compile time. The `PhantomData<S>` is
zero-sized — `size_of::<Connection<Open>>() == size_of::<Connection<Closed>>()`. The pattern shows
up in `bon`/`typed-builder` (states = "which required fields are set yet") and in protocol clients.
Full walkthrough, including the builder application and the variance table, in ref 01.

## Reference Files

| File | Consult When |
|------|--------------|
| `references/01-type-state-and-newtypes.md` | Typestate state machines, newtype + sealed + extension traits, typestate builders (bon/typed-builder), PhantomData & variance |
| `references/02-interior-mutability-and-raii.md` | Cell/RefCell/Mutex/RwLock/Atomic/OnceLock decision tree, RAII guards, Drop ordering & `mem::drop`, enum-driven runtime state machines |
| `references/03-error-architecture.md` | thiserror vs anyhow, error enums, `?`/`From` desugaring, `.context()`, library-vs-app boundary |
| `references/04-dispatch-and-traits.md` | dyn vs generic dispatch, object safety / dyn-compatibility rules, monomorphization tradeoffs, impl Trait / GAT / RPITIT, Deref for smart pointers + the anti-pattern |

## Examples

| Example | Walks Through |
|---------|---------------|
| `examples/typestate_request.rs` | Compilable type-state HTTP request builder — invalid order won't compile |
| `examples/error_architecture.rs` | thiserror library enum flowing into an anyhow application boundary via `?` + `.context()` |

## Sources

Every snippet in this skill is traceable to an official or canonical source — the Rust
API Guidelines, the Rust Reference, the Rustonomicon, the `std` docs, the rust-unofficial
*Rust Design Patterns* book, `docs.rs` crate docs (`thiserror`, `anyhow`, `bon`,
`typed-builder`), and the Rust release blog (GATs 1.65, RPITIT 1.75). Each reference file
carries inline URLs at the point of use.

Files in this skill

  • SKILL.md13 KB
  • examples/INDEX.md935 B
  • examples/error_architecture.rs5 KB
  • examples/typestate_request.rs3.1 KB
  • references/01-type-state-and-newtypes.md11.9 KB
  • references/02-interior-mutability-and-raii.md9.4 KB
  • references/03-error-architecture.md7.1 KB
  • references/04-dispatch-and-traits.md9.3 KB
  • references/INDEX.md1.5 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…