Skip to content
Development
Skill

/rust-patterns

Rust: ownership, lifetimes, async (Tokio), Result/anyhow/thiserror, traits, unsafe. Triggers: Rust, borrow checker, lifetime, Tokio, cargo, trait, impl, Result, unsafe, clippy.

From plugin
ai-toolkit
161111 skills44 agents
Install
$ npx -y skills add softspark/ai-toolkit --skill rust-patterns --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/rust-patterns

Context preview

The summary Claude sees to decide when to auto-load this skill.

Rust: ownership, lifetimes, async (Tokio), Result/anyhow/thiserror, traits, unsafe. Triggers: Rust, borrow checker, lifetime, Tokio, cargo, trait, impl, Result, unsafe, clippy.

SKILL.md

rust-patterns.SKILL.md
name: rust-patterns
description: "Rust: ownership, lifetimes, async (Tokio), Result/anyhow/thiserror, traits, unsafe. Triggers: Rust, borrow checker, lifetime, Tokio, cargo, trait, impl, Result, unsafe, clippy."
effort: medium
user-invocable: false
allowed-tools: Read

Rust Patterns

Project Structure

my-app/
├── Cargo.toml
├── src/
│   ├── main.rs           # Binary entry point
│   ├── lib.rs            # Library root (re-exports)
│   ├── error.rs          # Crate-level error types
│   ├── api/
│   │   ├── mod.rs
│   │   └── handlers.rs
│   └── domain/
│       ├── mod.rs
│       └── service.rs
├── tests/                # Integration tests (separate crate)
│   └── api_test.rs
├── benches/              # criterion benchmarks
│   └── throughput.rs
└── examples/
    └── demo.rs

Workspace layout for multi-crate projects:

# Cargo.toml (workspace root)
[workspace]
resolver = "2"
members = ["crates/core", "crates/api", "crates/cli"]

[workspace.dependencies]
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["full"] }

---

Idioms / Code Style

Ownership and Borrowing

// Borrow when you only need to read
fn print_name(name: &str) { println!("{name}"); }

// Take ownership when storing or consuming the value
fn register_user(name: String) -> User {
    User { name, id: Uuid::new_v4() }
}

Lifetimes

// Annotate only when the compiler cannot infer
struct Parser<'input> {
    source: &'input str,
    pos: usize,
}

impl<'input> Parser<'input> {
    fn next_token(&mut self) -> Option<&'input str> {
        let start = self.pos;
        // ... advance self.pos ...
        Some(&self.source[start..self.pos])
    }
}

Trait-Based Design

trait Repository {
    fn find_by_id(&self, id: Uuid) -> Result<Option<User>, DbError>;
    fn save(&self, user: &User) -> Result<(), DbError>;
}

// Accept generics for testability
fn create_user(repo: &impl Repository, name: String) -> Result<User, AppError> {
    let user = User::new(name);
    repo.save(&user)?;
    Ok(user)
}

Iterators, Pattern Matching, Newtype

// Iterator chains over manual loops
let active: Vec<&str> = users.iter()
    .filter(|u| u.is_active)
    .map(|u| u.email.as_str())
    .collect();

// Exhaustive matching
match command {
    Command::Start { port } => start_server(port),
    Command::Stop => shutdown(),
}

// let-else for early exit (Rust 1.65+)
let Some(cfg) = load_config() else { return Ok(Config::default()); };

// Newtype to prevent primitive misuse
struct UserId(Uuid);
struct Email(String);

impl Email {
    fn new(raw: &str) -> Result<Self, ValidationError> {
        if raw.contains('@') { Ok(Self(raw.to_lowercase())) }
        else { Err(ValidationError::InvalidEmail) }
    }
}

Builder Pattern

#[derive(Default)]
struct RequestBuilder { url: String, timeout: Option<Duration> }

impl RequestBuilder {
    fn url(mut self, url: impl Into<String>) -> Self { self.url = url.into(); self }
    fn timeout(mut self, d: Duration) -> Self { self.timeout = Some(d); self }
    fn build(self) -> Result<Request, BuildError> {
        if self.url.is_empty() { return Err(BuildError::MissingUrl); }
        Ok(Request { url: self.url, timeout: self.timeout.unwrap_or(Duration::from_secs(30)) })
    }
}

---

Error Handling

thiserror (libraries) vs anyhow (binaries)

use thiserror::Error;

#[derive(Debug, Error)]
pub enum AppError {
    #[error("not found: {0}")]
    NotFound(String),
    #[error("validation failed: {0}")]
    Validation(String),
    #[error("database error")]
    Database(#[from] sqlx::Error),
    #[error(transparent)]
    Unexpected(#[from] anyhow::Error),
}
// anyhow for application / binary code -- adds context to any error
use anyhow::{Context, Result};

fn load_config(path: &Path) -> Result<Config> {
    let content = fs::read_to_string(path)
        .with_context(|| format!("failed to read {}", path.display()))?;
    toml::from_str(&content).context("invalid TOML")
}

Error Propagation and Boundary Mapping

// ? converts and propagates via From impls
fn get_email(pool: &PgPool, id: Uuid) -> Result<String, AppError> {
    let user = sqlx::query_as!(User, "SELECT * FROM users WHERE id = $1", id)
        .fetch_optional(pool).await?
        .ok_or_else(|| AppError::NotFound(format!("user {id}")))?;
    Ok(user.email)
}

// Map domain errors to HTTP at the API boundary
impl IntoResponse for AppError {
    fn into_response(self) -> axum::response::Response {
        let (status, msg) = match &self {
            AppError::NotFound(m) => (StatusCode::NOT_FOUND, m.clone()),
            AppError::Validation(m) => (StatusCode::BAD_REQUEST, m.clone()),
            _ => (StatusCode::INTERNAL_SERVER_ERROR, "internal error".into()),
        };
        (status, Json(json!({ "error": msg }))).into_response()
    }
}

---

Testing Patterns

Unit Tests (inline module)

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn email_rejects_invalid() { assert!(Email::new("bad").is_err()); }

    #[tokio::test]
    async fn fetches_user() {
        let pool = setup_test_db().await;
        let user = get_user(&pool, test_id()).await.unwrap();
        assert_eq!(user.name, "Alice");
    }
}

Integration Tests (tests/ directory)

// tests/api_test.rs -- compiled as separate crate, only sees pub API
#[tokio::test]
async fn health_returns_200() {
    let app = my_app::app().await;
    let resp = app.oneshot(
        Request::builder().uri("/health").body(Body::empty()).unwrap()
    ).await.unwrap();
    assert_eq!(resp.status(), StatusCode::OK);
}

Mocking (mockall) and Property Testing (proptest)

use mockall::automock;

#[automock]
trait UserRepo { fn find(&self, id: Uuid) -> Result<Option<User>, DbError>; }

#[test]
fn returns_not_found_when_missing() {
    let mut mock = MockUserRepo::new();
Read more
Ships withai-toolkit

Professional-grade AI coding toolkit with multi-platform support. Machine-enforced safety, 109 skills, 44 agents, expanded lifecycle hooks, persona presets, experimental opt-in plugin packs, and benchmark tooling — works with Claude Code, Claude Chat/Cowork,

Get the whole plugin