Rust Coding Conventions
Read this skill before writing, reviewing, or refactoring Rust code in this project. It covers error handling, naming, module organization, formatting, testing, and more.
Rust Coding Conventions
General Principles:
- Follow standard Rust conventions (use
rustfmtandclippy) - Use idiomatic Rust patterns throughout
- Prefer
Result<T, E>for error handling over panics - Apply RAII principles through Rust's ownership system
- Use const-correctness via immutable references (
&) - Write self-documenting code with clear naming and structure
- Leverage the type system for compile-time safety
- Keep functions focused and modular
Error Handling:
Use
Result<T, E>for all fallible operationsDefine a project-wide
Result<T>type alias with unified error type:pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;Use
?operator for error propagationAvoid
.unwrap()in library code; only use in application entry points after proper error handlingUse
.ok_or_else()or.ok_or()to convertOptiontoResultwith meaningful error messagesProvide context when returning errors:
Err(format!("Failed to download {}: {}", url, e).into())Never panic in library code unless documenting preconditions with
#[panic]doc comments
Comparison and Conditional Expressions:
- Always use explicit boolean comparisons for clarity and consistency
- Use
== trueand== falseinstead of bare conditionals or negation - Examples:
- ✅ Correct:
if condition == true,if value == false - ❌ Incorrect:
if condition,if !value
- ✅ Correct:
- Exception: Direct variable tests in control flow are allowed when clearly intentional
- Apply to all boolean comparisons including
OptionandResultchecks - Use explicit comparisons with
None:if option_value.is_none() == trueorif option_value == None - Allow clippy warnings for explicit boolean comparisons with project-level configuration
Module Organization:
Use module structure to organize code by functionality
One public struct or major component per file
Related utility functions in dedicated
utils.rsModule declaration order in
lib.rs:- Private module declarations (
mod) - Public re-exports (
pub use) - Type aliases
- Private module declarations (
Example:
mod template_manager; mod utils; pub use template_manager::TemplateManager; pub use utils::copy_dir_all; pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
Functions and Methods:
Document all public APIs with doc comments (
///)Use doc comment structure:
- Brief one-line description (no explicit
# Descriptionheader) - Longer explanation if needed (separated by blank line)
# Argumentssection for parameters# Returnssection for return values (when non-obvious)# Errorssection for fallible functions# Examplessection when helpful# Panicssection if function can panic
- Brief one-line description (no explicit
Example:
/// 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 forCopytypesUse 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
Structs and Types:
Use clear, descriptive names for all types
Define fields in logical grouping order
Document struct purpose and usage with doc comments
Example:
/// Manages template files for coding agent instructions /// /// The `TemplateManager` handles all operations related to template storage, /// verification, backup, and synchronization. Templates are stored in the /// local data directory and backed up to the cache directory before modifications. pub struct TemplateManager { config_dir: PathBuf, cache_dir: PathBuf }Use
#[derive]for common traits when appropriateImplement
Defaultfor structs with sensible defaultsGroup related structs together in the same file when tightly coupled
Naming Conventions:
- Types (structs, enums, traits): Upper PascalCase (e.g.,
TemplateManager,FileMapping,Result) - Functions/methods: snake_case (e.g.,
download_file,create_backup,load_template_config) - Variables and function parameters: snake_case (e.g.,
config_dir,source_path,file_name) - Constants: UPPER_SNAKE_CASE (e.g.,
MAX_WIDTH,DEFAULT_TIMEOUT) - Type parameters: Single uppercase letter or PascalCase (e.g.,
T,E,Error) - Lifetimes: Short lowercase names (e.g.,
'a,'static) - Module names: snake_case (e.g.,
template_manager,utils)
Enums and Pattern Matching:
Use descriptive variant names in PascalCase
Derive common traits when appropriate
Use
#[derive(Debug)]for all types when possible for better error messagesUse exhaustive pattern matching; avoid
_ =>catch-alls when possibleUse
if letfor single-pattern matchingUse
matchfor multiple patterns or when you need exhaustiveness checkingUse
let...elsefor early returns with single pattern:let Some(value) = option else { return Err("Missing value".into()); };Prefer
Option<T>over sentinel enum variants. Do not addInvalid,Unknown, orNonevariants to an enum solely to avoid wrapping it inOption.Option<T>is niche-optimized (zero runtime cost for most enums) and forces callers to handle absence at compile time, whereas sentinel variants move that guarantee to a runtime convention and pollute every match site with a defensive arm.- ❌ Incorrect:
enum Color { Invalid, Red, Green, Blue }returned from a parser - ✅ Correct:
enum Color { Red, Green, Blue }withOption<Color>at the boundary - Exception: when "unknown" is a meaningful domain state — e.g. forward-compatible protocol parsing where unrecognized variants must round-trip — model it explicitly (
HttpVersion::Unknown(String)). This is "modeling the domain accurately," not "avoidingOption."
- ❌ Incorrect:
CLI Design with clap:
Use clap's derive API for argument parsing
Define main CLI struct with
#[derive(Parser)]Use
#[derive(Subcommand)]for command structureAdd helpful descriptions with
#[command]attributesExample:
#[derive(Parser)] #[command(name = "my-app")] #[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
--helpoutput
Formatting Configuration (.rustfmt.toml):
- Use project-specific rustfmt configuration for consistency
- Key formatting rules:
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 logically
- Run
cargo fmtbefore committing code - Configure editor to format on save
Imports and Dependencies:
Group imports in order:
- Standard library (
std::) - External crates (alphabetically)
- Project modules (
crate::)
- Standard library (
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.rsfor convenience
Conditional Compilation and Features:
Use feature flags for optional functionality
Document feature requirements in doc comments
Use
#[cfg(feature = "...")]for conditional codeSpecify features in
Cargo.tomldependencies when needed:reqwest = { version = "0.12", features = ["blocking", "json"] }
Testing:
Write unit tests alongside implementation in the same file
Use
#[cfg(test)]module for testsName test functions descriptively:
test_<scenario>_<expected_outcome>Use
assert!,assert_eq!,assert_ne!macrosTest both success and error cases
Example:
#[cfg(test)] mod tests { use super::*; #[test] fn test_parse_github_url_valid() { // Test implementation } }
Comments and Documentation:
Use
///for public API documentation (appears in generated docs)Use
//!for module-level documentation at file topUse
//for implementation comments and explanationsDocument 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 my-app /// 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 }
Linting Configuration:
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
File Organization:
Entry point:
src/main.rs(minimal, delegates to library)Library API:
src/lib.rs(public interface)Implementation: Feature modules in
src/Keep
main.rsfocused on CLI handling and error reportingPut 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
Best Practices:
Use
std::env::current_dir()over hardcoding pathsUse
PathandPathBuffor filesystem pathsLeverage
std::io::Writetrait for flushing output buffersUse
owo-colorsor similar crate for terminal output stylingUse platform-appropriate paths via
dirscrate (prefer over$HOMEenv 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
Error Messages:
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());
Version and Edition:
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
Code Review Checklist:
- All public APIs have doc comments
- Error handling uses
Resultconsistently - No
.unwrap()calls in library code - Explicit boolean comparisons used throughout
- Code formatted with
cargo fmt - No clippy warnings (or explicitly allowed with reasoning)
- Tests pass with
cargo test - Code builds in both debug and release modes
- Imports organized and minimal
- Functions are focused and modular
Source: heikopanjas/slopctl — distributed by TomeVault.