rust-patterns
SKILL.md
Rust Patterns
Modern Rust patterns and best practices.
Error Handling
Custom Error Types with thiserror
use thiserror::Error;
#[derive(Error, Debug)]
pub enum AppError {
#[error("Not found: {0}")]
NotFound(String),
#[error("Unauthorized: {0}")]
Unauthorized(String),
#[error("Database error")]
Database(#[from] sqlx::Error),
#[error("IO error")]
Io(#[from] std::io::Error),
}
Propagation with ?
fn read_config(path: &str) -> Result<Config, AppError> {
let content = std::fs::read_to_string(path)?; // Auto-converts io::Error
let config: Config = serde_json::from_str(&content)
.map_err(|e| AppError::NotFound(format!("Invalid config: {e}")))?;
Ok(config)
}
Option Combinators
fn find_user(id: &str) -> Option<User> {
users.iter()
.find(|u| u.id == id)
.filter(|u| u.is_active)
.map(|u| u.clone())
}
// Chain with unwrap_or, unwrap_or_else, unwrap_or_default
let name = user.name.as_deref().unwrap_or("Anonymous");
Ownership and Borrowing
Prefer Borrowing
// Good: Borrows data, doesn't take ownership
fn process(data: &[u8]) -> Result<Output, Error> { /* ... */ }
// Good: Mutable borrow when needed
fn update(config: &mut Config) { /* ... */ }
// Take ownership only when necessary (storing, sending to thread)
fn spawn_worker(data: Vec<u8>) { /* ... */ }
Builder Pattern
pub struct ServerBuilder {
addr: String,
port: u16,
max_connections: Option<usize>,
}
impl ServerBuilder {
pub fn new(addr: impl Into<String>) -> Self {
Self {
addr: addr.into(),
port: 8080,
max_connections: None,
}
}
pub fn port(mut self, port: u16) -> Self {
self.port = port;
self
}
pub fn max_connections(mut self, max: usize) -> Self {
self.max_connections = Some(max);
self
}
pub fn build(self) -> Server {
Server { /* ... */ }
}
}
// Usage: Server::builder("localhost").port(3000).build()
Traits
Define Behavior, Not Data
pub trait Repository {
type Error;
fn find_by_id(&self, id: &str) -> Result<Option<User>, Self::Error>;
fn save(&self, user: &User) -> Result<(), Self::Error>;
fn delete(&self, id: &str) -> Result<bool, Self::Error>;
}
Blanket Implementations
// Implement for all types that satisfy bounds
impl<T: Display + Debug> Loggable for T {
fn log(&self) {
println!("[LOG] {self}");
}
}
Async Rust
Tokio Patterns
use tokio::task::JoinSet;
async fn fetch_all(urls: Vec<String>) -> Vec<Result<Response, Error>> {
let mut set = JoinSet::new();
for url in urls {
set.spawn(async move {
reqwest::get(&url).await?.text().await
});
}
let mut results = Vec::new();
while let Some(result) = set.join_next().await {
results.push(result.unwrap());
}
results
}
Graceful Shutdown
use tokio::signal;
async fn run_server() -> Result<(), Error> {
let server = start_server().await?;
signal::ctrl_c().await?;
println!("Shutting down...");
server.shutdown().await;
Ok(())
}
Tooling
| Tool | Purpose | Command |
|---|---|---|
| cargo check | Type checking | cargo check |
| cargo clippy | Linting | cargo clippy -- -D warnings |
| cargo fmt | Formatting | cargo fmt -- --check |
| cargo test | Testing | cargo test |
| cargo audit | Security | cargo audit |
Checklist
-
clippypasses with no warnings - Error types defined with
thiserror -
?operator for error propagation - Prefer borrowing over ownership transfer
-
Cloneonly when necessary -
Send + Syncbounds for async types - No
unwrap()in library code (use?orexpect) - Builder pattern for types with many options
Weekly Installs
2
Repository
peopleforrester…dotfilesGitHub Stars
1
First Seen
Feb 28, 2026
Security Audits
Installed on
opencode2
gemini-cli2
codebuddy2
github-copilot2
codex2
kimi-cli2