/rust-patterns
Rust: ownership, lifetimes, async (Tokio), Result/anyhow/thiserror, traits, unsafe. Triggers: Rust, borrow checker, lifetime, Tokio, cargo, trait, impl, Result, unsafe, clippy.
$ npx -y skills add softspark/ai-toolkit --skill rust-patterns --agent claude-codeHow 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.mdname: 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.rsWorkspace 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
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.rsWorkspace 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();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,
Repo: softspark/ai-toolkit
Other skills on ai-toolkit.
- /ai-toolkit-rules
Mandatory engineering, security, testing, git, performance, quality, and response rules. Claude MUST load this skill for every technical, coding, debugging, review, architecture, DevOps, data, or file-editing task in Chat or Cowork.
Open skill - /mem-search
Search past coding sessions using natural language. Finds relevant observations, decisions, and context from previous work.
Open skill - /a11y-validate
Accessibility validator: WCAG 2.1 AA, EN 301 549, EAA. Triggers: a11y, accessibility, WCAG, EAA, ARIA, contrast, keyboard, screen reader.
Open skill - /agent-creator
Creates new specialized agents with frontmatter, tools, delegation. Triggers: new agent, create agent, agent scaffold, specialized agent.
Open skill - /analyze
Analyzes code quality, complexity, patterns across codebase. Triggers: quality report, hotspot scan, code analysis, architecture signal.
Open skill - /api-patterns
REST/GraphQL API design: naming, versioning, pagination, idempotency, OpenAPI. Triggers: API design, REST, GraphQL, OpenAPI, Swagger, idempotency, rate limit.
Open skill

