rust-conventions/SKILL.md
Rust coding conventions and best practices for idiomatic Rust development. Use when writing, reviewing, or refactoring Rust code to ensure consistency with error handling, RAII principles, naming conventions, and Rust edition best practices.
npx skillsauth add heikopanjas/agent-skills rust-conventionsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
rustfmt and clippy)Result<T, E> for error handling over panics&)UpdateOptions) to aggregate related arguments rather than passing many individual parameters. Prefer a single source of truth for data.Use Result<T, E> for all fallible operations
Use anyhow crate for error handling; re-export from lib.rs:
pub use anyhow::Result;
Use anyhow!() macro for constructing errors:
Err(anyhow!("Config file not found"))
Err(anyhow!("Failed to download {}: {}", url, e))
Use ? operator for error propagation
Avoid .unwrap() in library code; only use in application entry points after proper error handling
Use .ok_or_else() or .ok_or() to convert Option to Result with meaningful error messages
Never panic in library code unless documenting preconditions with #[panic] doc comments
Use the require! macro for precondition checks with early return:
require!(config_file.exists() == true, Err(anyhow!("Config not found")));
require!(name.is_empty() == false, None);
require!(count > 0, Ok(()));
require!(condition, return_expression)Result, Option, or bare valuesrequire! only for precondition checks at the top of a function (before any real work), mimicking design-by-contractrequire! for conditional logic deep inside function bodies; those should remain as regular if blocks== true and == false instead of bare conditionals or negationif condition == true, if value == falseif condition, if !valueOption and Result checksNone: if option_value.is_none() == true or if option_value == NoneAvoid if condition { continue; } guards at the top of loop bodies; they add visual noise especially with AlwaysNextLine brace style
Instead, combine guard conditions with the subsequent logic using &&, if/else if/else chains, or let-chains
Examples:
❌ Incorrect:
for entry in &files
{
if entry.is_skippable() == true
{
continue;
}
if let Some(value) = entry.process()
{
handle(value);
}
}
✅ Correct:
for entry in &files
{
if entry.is_skippable() == false &&
let Some(value) = entry.process()
{
handle(value);
}
}
For multi-branch dispatch, use if/else if/else instead of continue to skip to the next branch
Exception: continue inside match error arms (log-and-skip) is acceptable since it serves as early return from an error handler, not a guard
Use module structure to organize code by functionality
One public struct or major component per file
Related utility functions in dedicated utils.rs
Module declaration order in lib.rs:
mod)pub use)Example:
mod template_manager;
mod utils;
pub use anyhow::Result;
pub use template_manager::TemplateManager;
pub use utils::copy_dir_all;
Document all public APIs with doc comments (///)
Use doc comment structure:
# Description header)# Arguments section for parameters# Returns section for return values (when non-obvious)# Errors section for fallible functions# Examples section when helpful# Panics section if function can panicExample:
/// Creates a new TemplateManager instance
///
/// Initializes paths to local data and cache directories using the `dirs` crate.
/// Templates are stored in the local data directory and backups in the cache directory.
///
/// # Errors
///
/// Returns an error if the local data directory cannot be determined
pub fn new() -> Result<Self>
Pass by reference (&) for complex types, by value for Copy types
Use immutable references (&) unless mutation is required (&mut)
Keep function signatures on one line when under max width (167 chars)
Private helper functions should have single-line doc comments when logic is non-trivial
#[derive] for common traits when appropriateDefault for structs with sensible defaultsOption; use empty collections instead:
Option<Vec<T>>, Option<HashMap<K,V>> — creates redundant states (None vs empty)Vec<T>, HashMap<K,V> — empty collection represents absence#[serde(default, skip_serializing_if = "Vec::is_empty")] or "HashMap::is_empty"Option is appropriate for non-collection types where the default/zero value differs from absence (e.g., Option<Config>)Vec<T> via a getter, return &[T] (slice) not &Vec<T>TemplateManager, FileMapping, Result)download_file, create_backup, load_template_config)config_dir, source_path, file_name)MAX_WIDTH, DEFAULT_TIMEOUT)T, E, Error)'a, 'static)template_manager, utils)Use descriptive variant names in PascalCase
Derive common traits when appropriate
Use #[derive(Debug)] for all types when possible for better error messages
Use exhaustive pattern matching; avoid _ => catch-alls when possible
Use if let for single-pattern matching
Use match for multiple patterns or when you need exhaustiveness checking
Use let...else for early returns with single pattern:
let Some(value) = option else {
return Err(anyhow!("Missing value"));
};
Use clap's derive API for argument parsing
Define main CLI struct with #[derive(Parser)]
Use #[derive(Subcommand)] for command structure
Add helpful descriptions with #[command] attributes
Example:
#[derive(Parser)]
#[command(name = "vibe-check")]
#[command(about = "A manager for coding agent instruction files", long_about = None)]
struct Cli
{
#[command(subcommand)]
command: Commands
}
Use clear, descriptive field names that match CLI conventions
Provide defaults with #[arg(default_value = "...")]
Add documentation comments to show in --help output
max_width = 167 - Allow longer lines for readabilitybrace_style = "AlwaysNextLine" - Opening braces on new linescontrol_brace_style = "AlwaysNextLine" - Consistent brace placementtrailing_comma = "Never" - No trailing commasedition = "2024" - Use latest Rust editiontab_spaces = 4 - Standard indentationimports_granularity = "Crate" - Group imports by crategroup_imports = "StdExternalCrate" - Organize imports logicallycargo fmt before committing codeGroup imports in order:
std::)crate::)Use explicit imports over glob imports
Example:
use std::{
fs,
io::{self, Write},
path::{Path, PathBuf}
};
use chrono::{DateTime, Utc};
use owo_colors::OwoColorize;
use serde::{Deserialize, Serialize};
use crate::{Result, utils::copy_dir_all};
Re-export commonly used items from lib.rs for convenience
Use feature flags for optional functionality
Document feature requirements in doc comments
Use #[cfg(feature = "...")] for conditional code
Specify features in Cargo.toml dependencies when needed:
reqwest = { version = "0.12", features = ["blocking", "json"] }
Write unit tests alongside implementation in the same file
Use #[cfg(test)] module for tests
Name test functions descriptively: test_<scenario>_<expected_outcome>
Use assert!, assert_eq!, assert_ne! macros
Test both success and error cases
Example:
#[cfg(test)]
mod tests
{
use super::*;
#[test]
fn test_parse_github_url_valid()
{
// Test implementation
}
}
Use /// for public API documentation (appears in generated docs)
Use //! for module-level documentation at file top
Use // for implementation comments and explanations
Document the "why" not the "what" in implementation comments
Keep comments up-to-date with code changes
Use full sentences with proper punctuation in doc comments
Example:
//! Template management functionality for vibe-check
/// Creates a timestamped backup of a directory
///
/// Backups are stored in the cache directory with timestamp: `backups/YYYY-MM-DD_HH_MM_SS/`
fn create_backup(&self, source_dir: &Path) -> Result<()>
{
// Skip backup if source doesn't exist
if source_dir.exists() == false
{
return Ok(());
}
// ... rest of implementation
}
Allow specific clippy lints when project style differs from defaults
Configure in Cargo.toml:
[lints.clippy]
bool_comparison = "allow"
Can also use module-level attributes:
#![allow(clippy::bool_comparison)]
Document reasoning for lint exceptions
Entry point: src/main.rs (minimal, delegates to library)
Library API: src/lib.rs (public interface)
Implementation: Feature modules in src/
Keep main.rs focused on CLI handling and error reporting
Put business logic in library modules for reusability
Example structure:
src/
├── main.rs # CLI entry point
├── lib.rs # Public API
├── template_manager.rs # Core functionality
└── utils.rs # Shared utilities
Use std::env::current_dir() over hardcoding paths
Use Path and PathBuf for filesystem paths
Use Path::starts_with() for path prefix/subpath checks; avoid string-based path comparison (e.g. path.starts_with("foo/")) to ensure cross-platform behavior (Windows uses \, Unix uses /)
When resolving placeholders in paths (e.g. $workspace/AGENTS.md), use Path::join() with the suffix instead of string replace; string replace can produce mixed separators on Windows
Leverage std::io::Write trait for flushing output buffers
Use owo-colors or similar crate for terminal output styling
Use platform-appropriate paths via dirs crate (prefer over $HOME env var)
Implement flush() when printing without newline for immediate output:
print!("{} Processing... ", "→".blue());
io::stdout().flush()?;
Use early returns to reduce nesting depth
Prefer iterators and functional patterns over loops when clear
Use colored output for user-facing messages (owo-colors)
Format: "{} {}", symbol.color(), message.color()
Symbols: ✓ (success/green), ✗ (error/red), → (info/blue), ! (warning/yellow), ? (prompt/yellow)
Provide actionable error messages
Include file paths and operation details in errors
Example:
println!("{} Creating backup in {}", "→".blue(), backup_dir.display().to_string().yellow());
eprintln!("{} Failed to download {}: {}", "✗".red(), url, error.to_string().red());
Use Rust 2024 edition for latest language features
Specify in Cargo.toml:
[package]
edition = "2024"
Keep dependencies up-to-date but specify versions explicitly
Use semantic versioning in package version
Result consistently.unwrap() calls in library codecargo fmtcargo testtesting
Record recent changes and decisions in the AGENTS.md file. Use when making project decisions, choosing technologies, establishing conventions, or completing significant changes that should be tracked in the project history.
development
Swift coding conventions and best practices for modern Swift development. Use when writing, reviewing, or refactoring Swift code to ensure consistency with naming conventions, access control, async/await patterns, and SwiftUI/framework best practices.
development
Determine whether a version bump is required after code changes and apply semantic versioning (SemVer). Use when making code changes, fixing bugs, adding features, or introducing breaking changes to a project that uses SemVer.
development
Commit specific staged changes to a git repository using conventional commits format. Use when the user asks to commit, stage, or check in particular files or changes.