Rust Clean Implementation
When to Use This Skill
Read this skill when implementing new Rust code (not tests or async). For testing, see rust-testing-excellence. For async code, see rust-with-async-code.
🎯 Core Principles (CRITICAL - Always Apply)
1. Dependency Hierarchy: Project → Stdlib → External 🚨
MANDATORY: Check what the project already has before adding dependencies.
1. Project modules/crates (FIRST - search codebase)
↓ Can't fulfill need?
2. Rust stdlib (SECOND - use std::* when possible)
↓ Can't fulfill need?
3. External crates (LAST RESORT - truly necessary)
Quick check:
# Search for existing types
rg "struct.*Http" --type rust
rg "enum.*Error" --type rust
📖 Read when adding dependencies: dependency-hierarchy.md - Complete examples and decision process
2. Iron Rule: Always Use tracing Crate for Logging 🚨
NON-NEGOTIABLE: ALL logging MUST use the tracing crate macros. No exceptions.
Required dependencies in EVERY crate:
[dependencies]
tracing = { version = "0.1" }
[dev-dependencies]
tracing-test = { version = "0.2.5", features = ["no-env-filter"] }
serial_test = "3.3.1"
Use tracing macros for ALL logging:
use tracing::{debug, error, info, trace, warn};
// Info for important runtime events
info!("Server started on port {}", port);
info!("Model downloaded to: {}", path.display());
// Debug for development/diagnostic info
debug!("Processing request: {:?}", request);
// Trace for fine-grained tracing
trace!("Entering function with args: {:?}", args);
// Warn for recoverable issues
warn!("Retrying failed operation (attempt {}/{})", attempt, max_attempts);
// Error for actual errors
error!("Failed to connect: {}", error);
Tests MUST use #[traced_test]:
use tracing_test::traced_test;
#[test]
#[traced_test]
fn test_something() {
info!("Test started");
// Test code - logs will appear in test output
}
Why tracing:
- Structured logging with spans for better debugging
- Async-safe (unlike
println!orlog) - Zero overhead when not collecting traces
- Integrates with observability tools
- Project standard - consistency matters
Forbidden:
- ❌
println!()/eprintln!()(except for CLI output) - ❌
dbg!()in production code (fine for temporary debugging) - ❌
logcrate macros (log::info!, etc.) - ❌ Any other logging crate
📖 Read for complete guide: tracing-logging.md
3.0 Things to note
- Implement Debug and Display for structs, so they can be printed in logs always.
- Use derive_more for custom errors and its supplied capabilities
- Preferrably unless specified not to, use foundation_errstack for ewe_platform projects.
- For ewe_platform always read the valtron skills.
- Unless the user indicates, no clippy, cargo checks, build errors are pre-existing, fix it (some dead_code suppression is allowed, confirm with users) but everything else should be fixed and not suppresed.
3. Documentation: WHY/WHAT/HOW Pattern
Every public item needs documentation:
/// WHY: Validates user input to prevent injection attacks
///
/// WHAT: Checks that input contains only alphanumeric characters
///
/// HOW: Uses regex pattern `^[a-zA-Z0-9]+$`
///
/// # Errors
/// Returns `Error::InvalidInput` if input contains special characters
///
/// # Panics
/// Never panics
pub fn validate_input(input: &str) -> Result<(), Error> {
// Implementation
}
Mandatory sections:
- ✅ WHY - Purpose and motivation
- ✅ WHAT - What it does (one sentence)
- ✅ HOW - How it works (algorithm/approach)
- ✅ Errors - Document all error cases
- ✅ Panics - Document panic conditions (or state "Never panics")
📖 Read for complete patterns: documentation-patterns.md
4. Error Handling with derive_more
Use derive_more for clean error types:
use derive_more::{Display, Error, From};
#[derive(Debug, Display, Error, From)]
pub enum Error {
#[display(fmt = "invalid input: {}", _0)]
InvalidInput(String),
#[display(fmt = "not found")]
NotFound,
#[from]
Io(std::io::Error),
}
Pattern:
- Use
derive_morefor Display/Error/From - Provide context in display messages
- Use
#[from]for automatic conversions - Always implement Debug
📖 Read for complete guide: error-handling-guide.md
5. No_std Support (When Required)
Pattern for libraries:
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(not(feature = "std"))]
use core::{fmt, result};
#[cfg(feature = "std")]
use std::{fmt, result};
[features]
default = ["std"]
std = []
📖 Read when implementing: no-std-support.md - Complete patterns and testing
📚 Implementation Patterns (Read When Needed)
Security Best Practices
When to read: Before handling user input, crypto, or sensitive data
Quick checklist:
- Validate ALL user inputs
- Use
zeroizefor sensitive data - Avoid
unsafeunless absolutely necessary - Document security assumptions
📖 security-guide.md
Library-Owned CLI Pattern
When to read: When adding CLI subcommands that depend on library logic
Pattern: CLI command definitions (clap::Command) and handlers live in the library crate behind a cli feature flag. The platform binary is a thin wrapper that calls command() and run(). This allows the CLI to be used by standalone binaries or other consumers.
// In library crate (e.g., foundation_codegentools/src/cli/schema.rs):
#[must_use]
pub fn command() -> clap::Command { /* define args */ }
pub fn run(args: &clap::ArgMatches) -> Result<(), BoxedError> { /* handle */ }
// In library Cargo.toml:
// [features]
// cli = ["dep:clap"]
// In platform binary (thin wrapper):
pub fn register(command: clap::Command) -> clap::Command {
command.subcommand(foundation_codegentools::cli::schema::command())
}
pub fn run(args: &clap::ArgMatches) -> Result<(), BoxedError> {
foundation_codegentools::cli::schema::run(args)?;
Ok(())
}
Existing examples: foundation_testbed::cli, foundation_codegentools::cli
Iterator and Trait Patterns
When to read: Implementing custom iterators or traits
Quick example:
impl Iterator for MyType {
type Item = T;
fn next(&mut self) -> Option<Self::Item> { }
}
📖 iterator-patterns.md - Iterator implementations
📖 trait-patterns.md - Trait best practices
Performance Optimization
When to read: After profiling shows hot spots
Core rules:
- Measure first - Use criterion benchmarks
- Profile - Use flamegraph
- Optimize hot paths - Focus on frequent code
- Avoid premature optimization - Clarity first
📖 performance-tips.md - Complete patterns and benchmarking
✅ Implementation Checklist
Every new module/function must have:
-
tracingcrate in dependencies (mandatory for ALL crates) -
tracing-testandserial_testin dev-dependencies - All logging via tracing macros (
info!,debug!,error!,warn!,trace!) - Tests annotated with
#[traced_test] - WHY/WHAT/HOW documentation
- Error types with
derive_more - Errors section in docs
- Panics section in docs (or "Never panics")
- Used project types before external deps
- Security considerations (for user input/sensitive data)
- Tests (see testing skill)
Forbidden:
- ❌
println!()/eprintln!()for logging (except CLI output) - ❌
dbg!()in production code - ❌
logcrate or other logging crates - ❌ Undocumented public items
- ❌ Missing error documentation
- ❌ Adding external deps without checking project first
- ❌
unwrap()/expect()in library code (use?) - ❌ Ignoring no_std support (if project supports it)
📖 When to Read Example Files
Always check first:
- ⭐
dependency-hierarchy.md- Before adding ANY dependency - ⭐
tracing-logging.md- Mandatory for ALL crates
Read when you need to:
- Documentation:
documentation-patterns.md- WHY/WHAT/HOW patterns - Error handling:
error-handling-guide.md- derive_more examples - Security:
security-guide.md- Input validation, crypto, unsafe - No_std:
no-std-support.md- Supporting both std and no_std - Iterators:
iterator-patterns.md- Custom iterator implementation - Traits:
trait-patterns.md- Trait implementation patterns - Performance:
performance-tips.md- After profiling - Template:
basic-template.md- Starting a new module
🔗 Related Skills
- rust-testing-excellence - Writing tests
- rust-with-async-code - Async patterns
- rust-directory-and-configuration - Project structure