home / skills / phrazzld / claude-config / rust-patterns

rust-patterns skill

/skills/rust-patterns

This skill helps you write robust Rust patterns by teaching ownership, error handling, trait design, and configuration strategies.

npx playbooks add skill phrazzld/claude-config --skill rust-patterns

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
2.7 KB
---
name: rust-patterns
description: |
  Idiomatic Rust patterns for ownership, errors, traits, and configuration. Use when:
  - Writing or reviewing Rust code
  - Designing error handling with Result and thiserror
  - Implementing traits and composition patterns
  - Working with ownership and borrowing
  - Configuring Cargo features
  Keywords: Rust, ownership, borrowing, Result, thiserror, anyhow, trait,
  lifetimes, Cargo features, unsafe
effort: high
---

# Rust Patterns

Ownership-first, zero-cost abstractions, no hidden complexity.

## Error Handling

**Always use `Result<T, E>`. Never panic for expected failures:**
```rust
// Use thiserror for library error types
#[derive(Debug, thiserror::Error)]
pub enum UserError {
    #[error("user not found: {0}")]
    NotFound(String),
    #[error("invalid email format")]
    InvalidEmail,
    #[error("database error: {0}")]
    Database(#[from] sqlx::Error),
}

// Use anyhow for applications (context chaining)
fn fetch_user(id: &str) -> anyhow::Result<User> {
    let user = db.get(id)
        .context("fetching user from database")?;
    Ok(user)
}
```

**Propagate with `?`, add context at boundaries.**

## Ownership Patterns

**Borrowing > Cloning:**
```rust
// Good: Borrow for read-only
fn process(items: &[Item]) -> usize { ... }

// Good: Take ownership when storing/transforming
fn consume(items: Vec<Item>) -> Output { ... }

// Avoid: Excessive cloning
fn bad(items: &Vec<Item>) {
    let copy = items.clone(); // Usually unnecessary
}
```

**Fight the borrow checker → redesign, don't circumvent.**

## Trait Design

**Small, focused traits (1-3 methods):**
```rust
trait Readable {
    type Item;
    fn read(&self) -> Self::Item;
}

trait Writable {
    type Item;
    fn write(&mut self, item: Self::Item);
}

// Compose through bounds
fn copy<R, W>(src: &R, dst: &mut W)
where
    R: Readable<Item = Vec<u8>>,
    W: Writable<Item = Vec<u8>>,
{ ... }
```

**Consumer-side interfaces. Static dispatch by default.**

## Configuration

**Cargo features for compile-time options:**
```toml
[features]
default = ["json"]
json = ["serde_json"]
database = ["sqlx"]
full = ["json", "database"]
```

```rust
#[cfg(feature = "json")]
pub mod json_support { ... }
```

## Unsafe

**Minimize. Document with `// SAFETY:` comments:**
```rust
// SAFETY: We verified ptr is non-null and properly aligned
// in the caller's bounds check above
unsafe { *ptr }
```

Abstract behind safe interfaces.

## Anti-Patterns

- `unwrap()` / `expect()` for recoverable errors
- `Result<T, String>` (use typed errors)
- Excessive `Rc<RefCell<T>>` (redesign ownership)
- Monolithic traits (10+ methods)
- Reflection instead of generics
- Fighting borrow checker with unsafe

Overview

This skill provides idiomatic Rust patterns focused on ownership, error handling, trait design, and Cargo configuration. It distills practical rules and examples to help you write safer, clearer, and zero-cost Rust. Use it as a quick reference when coding, reviewing, or designing library and application boundaries.

How this skill works

It inspects common Rust workflows and recommends patterns: prefer Result<T, E> for recoverable errors, favor borrowing over cloning, design small composable traits, and use Cargo features for compile-time configuration. It highlights anti-patterns (e.g., unwrap(), Result<T, String>, excessive Rc<RefCell<T>>) and prescribes safer alternatives and documentation practices for unsafe code. Examples show concrete snippets for errors, ownership, traits, features, and safety comments.

When to use it

  • When writing or reviewing Rust library or application code
  • When designing error types and propagation using thiserror or anyhow
  • When implementing traits and choosing between static/dynamic dispatch
  • When reasoning about ownership, borrowing, and lifetimes to avoid cloning
  • When configuring optional functionality with Cargo features or auditing unsafe blocks

Best practices

  • Always return Result<T, E> for expected failures; use thiserror for typed library errors and anyhow for application-level context
  • Propagate errors with ? and add context at public boundaries with .context()
  • Prefer borrowing (&T, &[T]) for reads and take ownership (Vec<T>) for storage or transformation
  • Keep traits small and focused (1–3 methods) and compose via bounds; prefer static dispatch by default
  • Minimize unsafe; document invariants with // SAFETY: comments and expose a safe API

Example use cases

  • Designing a library error enum with thiserror and converting lower-level errors with #[from]
  • Refactoring code to replace unnecessary clones with borrowed slices or references
  • Defining small Readable/Writable traits and composing them for IO utilities with static dispatch
  • Adding optional JSON or database support behind Cargo feature flags and gating modules with #[cfg(feature)]
  • Auditing unsafe blocks: add SAFETY comments, limit surface area, and wrap unsafe in safe abstractions

FAQ

Should I ever use unwrap() or expect() in libraries?

No. Avoid unwrap()/expect() in libraries; return typed Result errors and let the caller decide. Use unwrap only in short-lived binaries or tests where failure should abort.

When to use anyhow vs thiserror?

Use thiserror to define typed error enums for libraries so callers can match or convert errors. Use anyhow in applications where boxed context-rich errors and ergonomic propagation are preferable.